Spring-AOP 动态切面

简介: Spring-AOP 动态切面

概述


低版本中,Spring提供了用于创建动态切面的DynamicMethodMatcherPointcutAdvisor抽象类,这个抽象类在2.0已过时,现在可以使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut来完成相同的功能。


DynamicMethodMatcherPointcut是一个抽象类,它将 isRuntime()标识位final并返回true,这样其子类就一定是一个动态切点。 该抽象类默认匹配所有的类和方法,因此需要扩展该类编写符合要求的动态切点


20170819122034230.jpg20170819122059390.jpg


实例


代码已托管到Github—> https://github.com/yangshangwei/SpringMaster


20170820121525009.jpg


目标:我们使用动态切面对特定的客户进行织入前置增强的横切代码。

我们先看下动态切点

package com.xgj.aop.spring.advisor.DynamicAdvisor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
    public static List<String> specialClientList = new ArrayList<String>();
    static {
        specialClientList.add("XiaoGongJiangOne");
        specialClientList.add("XiaoGongJiangTwo");
    }
    /**
     * (1)对类进行静态切点检查
     */
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                System.out.println("调用getClassFilter()对 类【 " + clazz.getName()
                        + "】做静态检查\n");
                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }
    /**
     * (2)对方法进行静态切点检查
     */
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        System.out
                .println("调用matches(Method method, Class<?> targetClass),对方法【"
                        + targetClass.getName() + "." + method.getName()
                        + "】做静态检查\n");
        return "greetTo".equals(method.getName());
    }
    /**
     * (3)对方法进行动态切点检查
     */
    @Override
    public boolean matches(Method method, Class<?> targetClass, Object... args) {
        System.out
                .println("调用matches(Method method, Class<?> targetClass, Object... args)对方法【"
                        + targetClass.getName()
                        + "."
                        + method.getName()
                        + "】做动态检查\n");
        String clientName = (String) args[0];
        return specialClientList.contains(clientName);
    }
}


我们可以看到GreetingDynamicPointcut 类既有用于静态切点检查的方法,又有动态切点检查的方法。


由于动态切点检查会对性能造成很大的影响,所以应当尽量避免在运行时每次都对目标类的各个方法进行动态检查。


Spring采用的机制如下: 在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就知可以知道连接点是不匹配的,这在运行时就会进行动态检查。 反之,则进行动态切点检查


在动态切点类中定义静态切点检查的方法可以避免不必要的动态检查操作,从而极大地提高运行效率。


我们在(3)处通过 matches(Method method, Class<?> targetClass, Object... args)定义了动态切点检查的方法,结合(2)处只对目标方法为greetTo(clientName)且clientName为特殊客户的方法启用增强,通过specialClientList模拟特殊的客户名单。


前置增强同之前的一样,我们来看下

package com.xgj.aop.spring.advisor.DynamicAdvisor;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        // 输出切点
        System.out.println("Pointcut:" + target.getClass().getName() + "."
                + method.getName());
        String clientName = (String) args[0];
        System.out.println("How are you " + clientName + " ?");
    }
}


编写好动态切点和增强后, 我们通过Spring配置文件中装配出一个动态切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 目标对象 -->
    <bean id="waiterTarget" class="com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter"/>
    <!-- 第一种写法 
    <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <bean class="com.xgj.aop.spring.advisor.DynamicAdvisor.GreetingDynamicPointcut"/>
        </property>
        <property name="advice">
            <bean class="com.xgj.aop.spring.advisor.DynamicAdvisor.GreetingBeforeAdvice"/>
        </property>
    </bean>  -->    
    <!-- 前置增强 -->
    <bean id="greetBeforeAdvice" class="com.xgj.aop.spring.advisor.DynamicAdvisor.GreetingBeforeAdvice"/>
    <!-- 切点 -->
    <bean id="greetingDynamicPointcut" class="com.xgj.aop.spring.advisor.DynamicAdvisor.GreetingDynamicPointcut"/>
    <!-- 切面 --> 
    <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut-ref="greetingDynamicPointcut"
        p:advice-ref="greetBeforeAdvice"/> 
    <!-- 代理类 -->
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="dynamicAdvisor"
        p:target-ref="waiterTarget"
        p:proxyTargetClass="true"/>
</beans>


我们可以看到动态切面的配置和静态切面的配置没有什么区别。 静态切面的配置可以参考 http://blog.csdn.net/yangshangwei/article/details/77393421 比对下,一样的。


使用org.springframework.aop.support.DefaultPointcutAdvisor定义切面,注入动态切点greetingDynamicPointcut,织入增强greetBeforeAdvice。 当然了DefaultPointcutAdvisor还有个order属性,用于定义切面的织入顺序。


现在我们写个测试类,运行下

package com.xgj.aop.spring.advisor.DynamicAdvisor;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DynamicAdvisorTest {
    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/aop/spring/advisor/DynamicAdvisor/conf-dynamicAdvisor.xml");
        Waiter waiter = ctx.getBean("waiter", Waiter.class);
        // list中的特殊客户
        waiter.greetTo("XiaoGongJiangOne");
        waiter.serverTo("XiaoGongJiangOne");
        // list中的特殊客户
        waiter.greetTo("XiaoGongJiangTwo");
        waiter.serverTo("XiaoGongJiangTwo");
        // 不在list中的客户
        waiter.greetTo("XiaoGongJiang1");
        waiter.serverTo("XiaoGongJiang1");
    }
}

运行结果:

2017-08-20 01:41:20,872  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3b293677: startup date [Sun Aug 20 01:41:20 BOT 2017]; root of context hierarchy
2017-08-20 01:41:20,989  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/aop/spring/advisor/DynamicAdvisor/conf-dynamicAdvisor.xml]
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做静态检查
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做静态检查
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.toString】做静态检查
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.clone】做静态检查
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做静态检查
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangOne ?
Waiter Greet To XiaoGongJiangOne
调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做静态检查
Waiter Server To XiaoGongJiangOne
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangTwo ?
Waiter Greet To XiaoGongJiangTwo
Waiter Server To XiaoGongJiangTwo
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Waiter Greet To XiaoGongJiang1
Waiter Server To XiaoGongJiang1

我们来分析下:

System.out日志打印的前8行输出信息反应了在织入切面Spring对目标类中所有的方法进行的静态切点检查。

接下来的几行日志

调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做静态检查
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangOne ?
Waiter Greet To XiaoGongJiangOne


对Waiter.greetTo(“XiaoGongJiangOne”)第一次调用greetTo方法时,执行静态、动态切点检查

接下来的日志

调用getClassFilter()对 类【 com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter】做静态检查
调用matches(Method method, Class<?> targetClass),对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做静态检查
Waiter Server To XiaoGongJiangOne


对Waiter.serverTo(“XiaoGongJiangOne”)第一次调用serverTo方法时,执行静态切点检查.

接下来的日志

调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangTwo ?
Waiter Greet To XiaoGongJiangTwo


对Waiter.greetTo(“XiaoGongJiangOne”)第二次调用greetTo方法时,仅仅执行动态切点检查。

接下的日志

Waiter Server To XiaoGongJiangTwo


对Waiter.serverTo(“XiaoGongJiangOne”)第二次调用serverTo方法时,不再执行静态切点检查.

接下来的日志

调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Waiter Greet To XiaoGongJiang1


对waiter.greetTo(“XiaoGongJiang1”),第三次调greetTo方法时只执行动态切点检查

最后一行日志

Waiter Server To XiaoGongJiang1


第三次调用 serverTo,不再执行静态切点检查


通过上述的分析,结合GreetingDynamicPointcut切点类,可以很容易的发现,


Spring会在创建代理织入切面时,对目标类中的所有方法进行静态切点检查


在生成织入切面的代理对象后,第一次调用代理类的每一个方法都会进行一次静态切点检查,如果本次检查就能够从候选者列表中排除改方法,则以后对该方法就不会再执行静态切点检查


对于那些在静态切点检查时匹配的方法,在后续调用该方法时,将执行动态切点检查


在我们的案例中,切点匹配的规则是:

1.目标类为com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter或者其子类,

2.方法名为greetTo,

3.动态入参clientName必须是特殊名单中的客户。


基于这条规则,serverTo()以及Object继承而来的 toString和clone()方法通过静态切点检查就可以排除在候选者之外,只有greetTo方法是动态切点检查的候选者,每次调用都会进行动态切点检查。


如果我们将GreetingDynamicPointcut类中 对类和方法的静态切点注释掉,重新运行日志如下:

2017-08-20 02:20:53,908  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3b293677: startup date [Sun Aug 20 02:20:53 BOT 2017]; root of context hierarchy
2017-08-20 02:20:54,004  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/aop/spring/advisor/DynamicAdvisor/conf-dynamicAdvisor.xml]
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangOne ?
Waiter Greet To XiaoGongJiangOne
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo
How are you XiaoGongJiangOne ?
Waiter Server To XiaoGongJiangOne
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo
How are you XiaoGongJiangTwo ?
Waiter Greet To XiaoGongJiangTwo
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做动态检查
Pointcut:com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo
How are you XiaoGongJiangTwo ?
Waiter Server To XiaoGongJiangTwo
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.greetTo】做动态检查
Waiter Greet To XiaoGongJiang1
调用matches(Method method, Class<?> targetClass, Object... args)对方法【com.xgj.aop.spring.advisor.DynamicAdvisor.Waiter.serverTo】做动态检查
Waiter Server To XiaoGongJiang1


可以发现,每次调用代理对象的任何一个方法,都会执行动态切点检查,这将导致很大的性能问题。


结论


在定义动态切点时,切勿忘记同时覆盖getClassFilter()和 boolean matches(Method method, Class<?> targetClass)方法,通过静态切点检查排除大部分方法,从而提高程序运行效率

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
SQL 监控 Java
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面
|
XML Java 数据格式
技术好文:Spring基础篇——AOP切面编程
技术好文:Spring基础篇——AOP切面编程
|
Java API 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
1769 0
|
Java 开发者 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
622 0
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
382 2
Spring高手之路25——深入解析事务管理的切面本质
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
451 0
Spring高手之路22——AOP切面类的封装与解析
|
Java Spring 容器
SpringBoot整合AOP实现打印方法执行时间切面
SpringBoot整合AOP实现打印方法执行时间切面
229 1
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面