Spring 技术核心 IOC AOP <二> AOP详解

以下三个方面的关于AOP的总结全部转自JeffCoding的博客,个人觉得整理的非常容易理解,很明确。


<一>、Spring AOP的概念和使用

Spring的另一个核心的技术就是AOP,以下内容全部转JeffCoding的(http://blog.csdn.net/jeffleo/article/details/54136904)

一、AOP的核心概念

AOP(Aspect Oriented Programming),是面向切面编程的技术。AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP、AspectJ

AOP技术它利用一种称为“横切”的技术,剖解开封装的对象内部并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

这里写图片描述

1、切面(aspect)

散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice

2、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

3、连接点(joinpoint)

被拦截到的点,因为spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

拦截的方法,连接点拦截后变成切入点

6、目标对象(Target Object)

代理的目标对象,指要织入的对象模块,如上图的模块一、二、三

7、织入(weave)

通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程

8、AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理

这里写图片描述

五种类型的通知

  1. Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。 
    < aop:before>

  2. After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 
    < aop:after>

  3. After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。 
    < aop:after-returning>

  4. Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法 
    < aop:around>

  5. Afterthrowing advice:在方法抛出异常退出时执行的通知。 
    < aop:after-throwing>

这里写图片描述

什么时候该用AOP:

假如业务模块一、二、三都需要日志记录,那么如果都在三个模块内写日志逻辑,那么会有两个问题: 
1. 打破模块的封装性 
2. 有很多重复代码 
解决重复代码问题,可以通过封装日志逻辑为一个类,然后在各个模块需要的地方通过该类来试下日志功能,但是还是不能解决影响模块封装性的问题。 
那么AOP就可以解决,它使用切面,动态地织入到各模块中(实际就是使用代理来管理模块对象),这样既解决了重复代码问题又不会影响模块的封装性

二、Spring AOP

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理,要在Spring 中使用AOP,还需要加入这两个jar包 
1、aopalliance.jar 
2、aspectjweaver.jar

Spring中 AOP中的两种代理: 
1. Java动态代理,这样就可以为任何接口实例创建代理了,默认使用 
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

Spring AOP的使用步骤

一、定义具体业务逻辑模块(目标对象) 
二、定义切面(即实现通知逻辑) 
三、实现切面逻辑

三、基于Schema的Spring AOP实例

第一步、定义具体业务逻辑模块(目标对象)

两个业务逻辑模块都是基于接口

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第二步和第三步、 定义切面(即实现通知逻辑)

JointPoint是连接点,aop创建代理后会返回一个连接点,然后在通知中可以通过该连接点实现我们的切面逻辑

日志切面

public class LogAdivice{

    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //选择执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

时间切面:

public class TimeAdvice {

    public void timeBefore(){
        System.out.println("beforeTime = " + System.currentTimeMillis());
    }

    public void timeAfter(){
        System.out.println("afterTime = " + System.currentTimeMillis());
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在applicationContext中配置切面:

<context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <aop:config>
       <!-- 配置一个切面 -->
       <aop:aspect id="logaop" ref="logAdivice" order="2">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="myBeforeAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="myAfterAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义环绕通知 -->
           <aop:around method="myAroundAdivice" pointcut-ref="testpointcut"/>
       </aop:aspect>

       <!-- 定义另一个切面 -->
       <aop:aspect id="timeaop" ref="timeAdvice" order="1">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="timeBefore" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="timeAfter" pointcut-ref="testpointcut"/>
       </aop:aspect>
    </aop:config>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

当有多个切面时,Spring默认是按照切面定义的顺序来执行,也可以通过order属性来配置切面的执行属性,order=1 早于 order=2执行

测试结果

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TestAOPService service = (TestAOPService) context.getBean("testAOPService");
        service.addUser();
    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里写图片描述

四、基于@AspectJ注解的AOP实现

第一步、定义具体业务逻辑模块(目标对象)

第一步和上面一样

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第二步和第三步、 定义切面(即实现通知逻辑)

重点是定义切入点

@Aspect
public class LogAdivice{

    //定义一个方法作为切入点id
    @Pointcut("execution(* com.ssh.service.TestAOPService.*(..))")
    private void allMethod(){}

    @Before("allMethod()")
    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    @AfterReturning("allMethod()")
    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    @Around("allMethod()")
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在applicationContext的配置:

<!-- 打开自动扫描(隐式打开注解管理器) -->
    <!-- <context:component-scan base-package="com.ssh"/> -->
    <context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <!-- 打开aop注解管理器 -->
    <aop:aspectj-autoproxy/>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

五、Java代码使用AOP

public class TestControlFlowPointcut {

    public static void main(String[] args) {
        //只有TargetCaller中的方法才会被拦截
        ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
        BeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
            public void before(Method method, Object[] objects, Object o) throws Throwable {
                System.out.println(method.getClass().getSimpleName() + ":" +
                        method.getName() + " - before logic ");
            }
        };

        // Spring 中的 Aspect,装载pointcut和advice
        PointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);

        // Spring 基本织入器weaver
        ProxyFactory weaver = new ProxyFactory();
        weaver.setTarget(new TargetObject());   //指定代理目标对象
        weaver.addAdvisor(advisor);  //指定方面

        Object proxy = weaver.getProxy();

        //直接调用Targetobject的方法不会被拦截
        ((TargetObject)proxy).targetMethod();

        //使用ControlFlowPointcut指定的类中的方法才会被拦截
        TargetCaller caller = new TargetCaller();
        caller.setTarget((TargetObject)proxy);
        caller.callMethod();
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
<二>AOP的实现机制

以下内容与上面的内容转自同一个博主,转自JeffCoding(http://blog.csdn.net/jeffleo/article/details/61205623)

 spring AOP 属于第二代 AOP, 采用动态代理机制和字节码生成技术实现 。 
  与最初的 AspectJ 采用编译器将横切逻辑织入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象

一、动态代理

  我们可以为指定的接口在系统运行期间动态的生成代理对象, 从而帮助我们走出最初使用静态代理实现 AOP 的窘境 
  动态代理机制的实现主要由 Java.lang.reflect.Proxy 类和java.lang.reflect.InvocationHandler接口。 
  详细使用请看:Java设计模式之—静态代理和动态代理 
   
  动态代理的缺点:动态代理只能对实现了相应Interface的类使用,如果某个类没有实现任何的Interface,就无法使用动态代理对其产生相应的代理对象! 
  因此:在默认情况下,如果Spring AOP发现目标实现了相应的Interface,则采用动态代理为其生成代理对象实例;而如果目标对象没有实现任何的Interface,Spring AOP会尝试使用CGLIB动态字节码生成类库,为目标对象生成代理对象实例!

二、CGLIB动态字节码生成

  使用CGLIB扩展对象行为的原理是:对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式相同的效果了。 
  记住:相比于动态代理,CGLIB的优势就是,可以为没有实现任何接口的类进行扩展

  例如: 
  目标类如下:(这里不实现任何接口

public class Requestable {  

    public void request() {  
        System.out.println("requestable without implementint any interface");  
    }  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  要对该类进行继承扩展,所以我们要使用CGLIB类库,实现一个net.sf.cglib.proxy.Callback,或者使用net.sf.cglib.proxy.MethodInterceptor(继承自Callback)

import java.lang.reflect.Method;  

import org.joda.time.TimeOfDay;  

import net.sf.cglib.proxy.MethodInterceptor;  
import net.sf.cglib.proxy.MethodProxy;  

public class RequestCallback implements MethodInterceptor {  

    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
        if(method.getName().equals("request")) {  
            System.out.println("你调用了request方法")
            return proxy.invokeSuper(object, args);  
        }  
        return null;  
    }  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  到了这里,我们使用MethodInterceptor为request方法实现了对request请求进行控制的逻辑 
  然后我们现在通过Enhancer为目标对象动态生成一个子类,将RequestCallback的横切逻辑附加到该子类中:

import net.sf.cglib.proxy.Enhancer;  

public class Client {  

    public static void main(String[] args) {  
        Enhancer enhancer = new Enhancer();  
        //设置父类
        enhancer.setSuperclass(Requestable.class);  
        //设置Callback
        enhancer.setCallback(new RequestCallback());  

        Requestable proxy = (Requestable) enhancer.create();  
        proxy.request();  
    }  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  结果如下: 
  你调用了request方法 
  requestable without implementint any interface

  缺点:使用 CGLIB 对类进行扩展的唯一限制就是 无法对 final 方法进行覆写

三、总结

  以上两种技术(动态代理 与 动态字节码生成) 就是 Spring AOP 所使用的核心技术,也就是 Spring AOP 的Weaving And Weaver 的实现原理了

<三>  Spring AOP的底层实现原理

以下内容全部转自(http://blog.csdn.net/jeffleo/article/details/61208404)

一、前言

  前面第一篇我们讲到了AOP的概念和使用,第二篇也讲到了 AOP的实现机制,在第一篇,讲到了joinpoint,pointcut,aspect,weave等AOP的核心概念,接下来我们详解分析他们的实现原理! 
  在动态代理 和 CGLIB 的支持下, spring AOP 框架的实现经过了两代。从 Spring AOP 框架第一次发布,到 Spring 2.0 发布之前的 AOP 实现,是 Spring 第一代 AOP 实现。Spring 2.0 发布后的 AOP 实现是第二代。但是,Spring AOP 的底层实现机制一直没有变,唯一改变的,是各种 AOP 概念实现的表现形式以及 Spring AOP 的使用方式。 
  此篇介绍Joinpoint和PointCut: 

一、Joinpoint

  Spring AOP 中,仅支持方法级别的 Joinpoint ,更确切的说,只支持方法执行 (Method Execution )类型的 Joinpoint,虽然 Spring AOP 仅提供方法拦截,但是实际的开发过程中,这已经可以满足 80% 的开发需求了。 
  Spring AOP 之所以如此,主要有以下几个原因。 
  1. Spring AOP 要提供一个简单而强大的 AOP 框架,并不想因大而全使得框架本身过于臃肿。能够仅付出 20% 的努力,就能够得到 80% 的回报。否则,事倍功半,并不是想看到的结果。 
  2. 对于类中属性 (Field )级别的 Joinpoint ,如果提供这个级别的拦截,那么就破坏了面向对象的封装,而且,完全可以通过 setter 和 getter 方法的拦截达到同样的目的。 
  3. 如果应用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支持,可以求助于现有其他 AOP 实现产品,如 AspectJ。 目前看来, AspectJ 是 Java 平台对 AOP 支持最完善的产品,同时,Spring AOP 也提供了对 Aspect的支持。

二、Pointcut

  Spring中使用org.springframework.aop.Pointcut 作为其 AOP 框架中的所有 Pointcut 的最顶层抽象。

package org.springframework.aop;  

public interface Pointcut {  
    //用于匹配被织入操作的对象
    ClassFilter getClassFilter();  
    //用于匹配被织入操作的对象中的方法
    MethodMatcher getMethodMatcher();  

    Pointcut TRUE = TruePointcut.INSTANCE;  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  为什么要分开匹配?是为了可以重用不同级别的匹配定义,并且可以在不同的级别或者相同的级别上进行组合操作。

ClassFilter

package org.springframework.aop;  

public interface ClassFilter {  

    boolean matches(Class<?> clazz);  

    ClassFilter TRUE = TrueClassFilter.INSTANCE;  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  当织入的目标对象和Point指定的类型相同时,返回true,否则返回false,即意味着不会对这个类型的目标对象进行织入操作 
  比如,如果仅希望对系统中的 Foo 类型的对象执行织入,则可以

package prx.aop.proxy;  

import org.springframework.aop.ClassFilter;  

public class FooClassFilter implements ClassFilter{  

    public boolean matches(Class<?> clazz) {
        //只匹配Foo.class  
        return Foo.class.equals(clazz);  
    }  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  如果类型对所捕捉的 Joinpoint 无所谓,那么 Pointcut 中使用的 ClassFilter 可以直接使用ClassFilter TRUE = TrueClassFilter.INSTANCE

MethodMatcher

package org.springframework.aop;  

import java.lang.reflect.Method;  


public interface MethodMatcher {  

    boolean matches(Method method, Class<?> targetClass);  

    boolean isRuntime();  

    boolean matches(Method method, Class<?> targetClass, Object[] args);  

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  MethodMatcher 通过重载,定义了两个 matches 方法,而这两个方法的分界线就是 isRuntime 方法,这里要特别注意! 
  注意到三参数的matches方法中,最后一个参数是args,因此也就可以想到:两个 mathcers 方法的区别在于,在进行方法拦截的时候,是否匹配方法的参数 
  比如:现在要对 登录方法 login(String username, String passwod) 进行拦截 
  1. 只想在 login 方法之前插入计数功能,那么 login 方法的参数对于 Joinpoint 捕捉就是可以忽略的。 
  2. 在用户登录的时候对某个用户做单独处理(拒绝登录 或 给予特殊权限),那么方法的参数在匹配 Joinpoint 时必须要考虑到

  根据是否对方法的参数进行匹配,Pointcut可以分为StaticMethodMatcher和DynamicMethodMatcher,当isRuntime()返回false,表明不对参数进行匹配,为StaticMethodMatcher,返回true时,表示要对参数进行匹配,为DynamicMethodMatcher 
  一般情况下,DynamicMethodMatcher会影响性能,所以我们一般使用StaticMethodMatcher就行了

StaticMethodMatcher

  前一种情况下, isRuntime 返回 false , 表示不会考虑具体 Joinpoint 的方法参数, 这种类型的 MethodMatcher称之为 StaticMethodMatcher。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存以提高性能。

package org.springframework.aop.support;  

import java.lang.reflect.Method;  

import org.springframework.aop.MethodMatcher;  

public abstract class StaticMethodMatcher implements MethodMatcher {  

    public final boolean isRuntime() {  
        return false;  
    }  
    //三参数matches抛出异常,使其不被调用
    public final boolean matches(Method method, Class<?> targetClass, Object[] args) {  
        // should never be invoked because isRuntime() returns false  
        throw new UnsupportedOperationException("Illegal MethodMatcher usage");  
    }  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

DynamicMethodMatcher

  当 isRuntime 方法返回 true 时, 表明 MethodMatcher 将会每次都对方法调用的参数进行匹配检查,这种类型的MethodMatcher 称之为 DynamicMethodMatcher。 因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差。 
  注意: 
  如果一个 MethodMatcher 为 DynamicMethodMatcher , 那么只有 isRuntime 返回 true, 而且matchers(Method method, Class targetClass) 也返回 true 的时候, 三个参数的 matchers 方法将被执行,进行进一步检查匹配条件。否则不会执行 三个参数的 matchers 方法,直接返回 false 了。

package org.springframework.aop.support;  

import java.lang.reflect.Method;  

import org.springframework.aop.MethodMatcher;  

public abstract class DynamicMethodMatcher implements MethodMatcher {  

    public final boolean isRuntime() {  
        return true;  
    }  

    //DynamicMethodMatcher 中只有两参数的mathches方法return true,三参数才能被调用
    public boolean matches(Method method, Class<?> targetClass) {  
        return true;  
    }  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Pointcut家族

  在 MethodMatcher 类型的基础上, Pointcut 可以分为两类, 即 StaticMethodMatcherPointcut 和 DynamicMethodMatcherPointcut。 
  这里写图片描述

1. NameMatchMethodPointcut

  最简单的 Pointcut 实现,根据自身指定的一组方法名称与 Joinpoint 处的方法的名称进行匹配,支持“*”通配符实现简单模糊查询

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();  

pointcut.setMappedName("methodName");  

pointcut.setMappedNames(new String[]{"methodName1", "methodName2"});  

pointcut.setMappedNames(new String[]{"method*", "*Name", "method*Num"});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  但是, NameMatchMethodPointcut 无法对重载的方法名进行匹配, 因为它仅对方法名匹配,不考虑参数信息。

2. JdkRegexpMethodPointcut

  专门用于Java的正则表达式匹配型Point

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();  

pointcut.setPattern(".*method.*");  

pointcut.setPatterns(new String[]{".*method.*", ".*name.*"});  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

  注意:使用正则表达式来匹配对应的 Joinpoint 所处的方法时, 正则表达式的匹配模式必须以匹配整个方法签名的形式指定,而不能像 NameMatchMethodPointcut 那样仅给出匹配的方法名称。

public class Foo {  
    public void doSomething() {  

    }  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

  如果使用正则表达式 .doS. 则会匹配 Foo 的 doSomething 方法, 即完整签名:prx.aop.proxy.Foo.doSomething 。 但是如果 Pointcut 使用 doS.* 作为匹配的正则表达式模式,就无法捕捉到Foo 的 doSomething 方法的执行。

3. AnnotationMatchingPointcut

  根据对象是否有指定类型的注解来匹配Pointcut 
  有两种注解,类级别注解ClassLevelAnnotation,和方法级别注解MethodLevelAnnotation :

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface ClassLevelAnnotation {  

}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface MethodLevelAnnotation {  

} 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

  用法:

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的所有方法执行的时候,将全部匹配。  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);  

//还可以使用静态方法创建 pointcut 实例  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  


//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的方法(忽略类匹配)都将匹配  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);  


//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 同时标注了 MethodLevelAnnotation 的方法才会匹配  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  实例:

@ClassLevelAnnotation  
public class TargetObject {  

    @MethodLevelAnnotation  
    public void method1() {  
        System.out.println("target : method1");  
    }  

    public void method2() {  
        System.out.println("target : method2");  
    }  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
public class Client {  

    public static void main(String[] args) {      
        //pointcut 定义, 匹配方式可以按上面的说明修改,  这里是注解类的所有方法都匹配  
        AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  

        // advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑  
        BeforeAdvice advice = new MethodBeforeAdvice() {  
            public void before(Method method, Object[] args, Object target) throws Throwable {  
                System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");  
            }     
        };  

        // Spring 中的 Aspect , pointcut 和 advice 的封装类  
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();  
        advisor.setPointcut(pointcut);  
        advisor.setAdvice(advice);  

        // Spring 基本织入器 weaving 和 weaver  
        ProxyFactory weaver = new ProxyFactory();  
        weaver.setTarget(new TargetObject());   //指定代理目标对象  
        weaver.addAdvisor(advisor);             //指定 Aspect  

        Object proxyObject = weaver.getProxy(); //生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)  

        ((TargetObject) proxyObject).method1();  
        ((TargetObject) proxyObject).method2();  
    }  
}  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

4. ComposablePointcut

  ComposablePointcut 是 Spring AOP 提供的可以进行 Pointcut 逻辑运算的 Pointcut 实现, 它可以进行 Pointcut之间的 “并” 以及 “交” 运算。

ComposablePointcut pointcut1 = new ComposablePointcut(classFilter1, methodMatcher1);  
ComposablePointcut pointcut2 = new ComposablePointcut(classFilter2, methodMatcher2);  

//求并集  
ComposablePointcut unitedPointcut       = pointcut1.union(pointcut2);  
//求交集  
ComposablePointcut intersectionPointcut = pointcut1.intersection(unitedPointcut);  

assertEquals(pointcut1, intersectionPointcut); 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  Spring AOP 还提供了 工具类: org.springframework.aop.support.Pointcuts

Pointcut pointcut1 = ...;  
Pointcut pointcut2 = ...;  

//求并集  
Pointcut unitedPointcut = Pointcuts.union(pointcut1, pointcut2);  
//求交集  
Pointcut intersectionPointcut = Pointcuts.intersection(pointcut1, unitedPointcut);  

assertEquals(pointcut1, intersectionPointcut);  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5. ControlFlowPointcut

  非常有个性的 Pointcut 类型, 不是很常用。 指定只有当 Joinpoint 指定的某个方法 在 某个特定的 类中被调用时,才对其进行拦截 
  而一般情况是,Joinpoint 指定的方法,无论被谁调用,都会被拦截。 
  举例:

class TargetObject {

    public void targetMethod() {
        System.out.println("TargetObject : method1");
    }
}

//目标类
class TargetCaller {

    private TargetObject target;

    public void callMethod() {
        target.targetMethod();
    }

    public void setTarget(TargetObject target) {
        this.target = target;
    }
}

public class TestControlFlowPointcut {

    public static void main(String[] args) {
        //只有TargetCaller中的方法才会被拦截
        ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
        BeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
            public void before(Method method, Object[] objects, Object o) throws Throwable {
                System.out.println(method.getClass().getSimpleName() + ":" +
                        method.getName() + " - before logic ");
            }
        };

        // Spring 中的 Aspect
        PointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);

        // Spring 基本织入器 weaving 和 weaver
        ProxyFactory weaver = new ProxyFactory();
        weaver.setTarget(new TargetObject());   //指定代理目标对象
        weaver.addAdvisor(advisor);  //指定方面

        Object proxy = weaver.getProxy();

        //直接调用Targetobject的方法不会被拦截
        ((TargetObject)proxy).targetMethod();

        //使用ControlFlowPointcut指定的类中的方法才会被拦截
        TargetCaller caller = new TargetCaller();
        caller.setTarget((TargetObject)proxy);
        caller.callMethod();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

  结果:

TargetObject : method1
Method:targetMethod - before logic 
TargetObject : method1
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

  如果在 ControlFlowPointcut 的构造方法中单独指定 Class 类型的参数,如上面的例子,那么 ControlFlowPointcut将尝试匹配指定的 Class 中声明的所有方法,跟目标对象的 Joinpoint 处的方法流程组合。 所以,如果是想要做到“只有 TargetCaller 类的 callMethod 方法调用 TargetObject.method1() 才拦截,而 TargetCaller 的其他方法全都忽略” 的话,可以在构造时,传入第二个参数

ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class, "callMethod");  
 
 
  • 1
  • 1

  因为 ControlFlowPointcut 类型的 Pointcut 需要在运行期间检查程序的调用栈,而且每次方法调用都需要检查,所以性能比较差,应该尽量避免使用。

三、总结

  1. Pointcut最底层的是一个Pointcut接口,里面有对象匹配方法getClassFilter和方法匹配方法getMethodMatcher() 
  2. MethodMatcher接口中有三个方法,两参数和三参数的matches方法,和一个isRuntime方法,根据isRuntime返回值决定是否匹配方法参数从而决定调用哪个matches方法 
  3. Point中分为6种Point实现,分别是:NameMatchMethodPointcut、JdkRegexpMethodPointcut、Perl5RegexpMethodPoint、AnnotationMatchingPointcut、ComposablePointcut、ControlFlowPointcut,各自的特性在各自的名字中体现



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值