Spring-AOP 动态切面

  • 概述
  • 实例
  • 结论

概述

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

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

Spring-AOP 动态切面_第1张图片

Spring-AOP 动态切面_第2张图片


实例

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

Spring-AOP 动态切面_第3张图片

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

我们先看下动态切点

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 specialClientList = new ArrayList();

    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配置文件中装配出一个动态切面


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

你可能感兴趣的:(【Spring-AOP】,Spring-AOP基础手札,aop,动态切面)