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)方法,通过静态切点检查排除大部分方法,从而提高程序运行效率

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
8月前
|
XML 缓存 监控
切面的魔力:解密Spring AOP 面向切面编程
切面的魔力:解密Spring AOP 面向切面编程
32 0
|
19天前
|
XML 监控 Java
Spring AOP:解锁切面编程的威力与实践
Spring AOP:解锁切面编程的威力与实践
21 0
Spring AOP:解锁切面编程的威力与实践
|
2月前
|
存储 数据可视化 Java
自定义注解实现aop切面
自定义注解实现aop切面
25 1
|
4月前
|
Dubbo Java 应用服务中间件
微服务框架(八)Spring Boot AOP 日志切面实现
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为使用Spring Boot AOP 实现日志切面、分离INFO和ERROR级别日志
|
6月前
|
缓存 监控 安全
深入理解Spring Boot AOP:切面编程的优势与应用
深入理解Spring Boot AOP:切面编程的优势与应用
|
6月前
|
Java Maven Spring
AOP切面编程
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
51 0
|
9月前
|
XML Java 程序员
spring AOP切面编程
spring AOP切面编程
94 0
|
10月前
|
XML 开发框架 Java
《Spring6核心源码解析》已完结,涵盖IOC容器、AOP切面、AOT预编译、SpringMVC,面试杠杠的!
全网首个全面解析Spring6核心源码的专栏,涵盖:IOC容器、AOP切面、声明式事务、AOT预编译和SpringMVC,让你从根本上彻底掌握Spring6核心技术。
320 1
《Spring6核心源码解析》已完结,涵盖IOC容器、AOP切面、AOT预编译、SpringMVC,面试杠杠的!
|
10月前
|
XML SpringCloudAlibaba 监控
Spring AOP切面编程实现原理
`Spring AOP`是`Spring`框架中极为重要的核心功能,和`Spring IOC`并称为`Spring`的两大核心模块。顾名思义,**AOP 即 Aspect Oriented Programming,翻译为面向切面编程**。OOP面向对象编程是纵向地对一个事物的抽象,一个对象包括静态的属性信息、动态的方法信息等。而AOP是横向地对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程。
841 0
|
12月前
|
XML Java 数据格式
Spring-AOP 基于Schema配置切面
Spring-AOP 基于Schema配置切面
44 0