AOP

2016/4/7 posted in  spring

为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

静态代理和动态代理

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了

    • 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。
    • 缺点:
      • 一个代理类只能为一个接口服务
      • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法

代理接口

public interface Subject {
    public void dealTask(String taskName);
}

真正执行任务的类

public class RealSubject implements Subject {  
    @Override  
    public void dealTask(String taskName) {  
     System.out.println("正在执行任务:"+taskName);  
     try {  
        Thread.sleep(500);  
     } catch (InterruptedException e) {  
        e.printStackTrace();  
     }  
    }  
}  

代理类

public class ProxySubject implements Subject {  
    //代理类持有一个委托类的对象引用  
    private Subject delegate;  
       
    public ProxySubject(Subject delegate) {  
        this.delegate = delegate;  
    }  
      
    /** 
    * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 
    *  
    * @param taskName 
    */  
    @Override  
    public void dealTask(String taskName) {  
        long stime = System.currentTimeMillis();   
        //将请求分派给委托类处理  
        delegate.dealTask(taskName);  
        long ftime = System.currentTimeMillis();   
        System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    }  
}  

静态代理类工厂

public class SubjectStaticFactory {  
    //客户类调用此工厂方法获得代理对象。  
    //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
    public static Subject getInstance(){   
        return new ProxySubject(new RealSubject());  
    }  
}  

客户类

public class Client1 {  
    public static void main(String[] args) {  
        Subject proxy = SubjectStaticFactory.getInstance();  
        proxy.dealTask("DBQueryTask");  
    }   
}
  • 动态代理:在程序运行时,运用反射机制动态创建而成
    1. jdk动态代理 只能代理实现接口的类,不能实现接口的类就不能实现JDK的动态代理
    2. CGLib动态代理

Spring AOP:前置增强、后置增强、环绕增强,抛出增强

  • 前置增强类实现了org.springframework.aop.MethodBeforeAdvice接口
  • 后置增强类实现了org.springframework.aop.AfterReturningAdvice接口
  • 环绕增强类需要实现org.aopalliance.intercept.MethodInterceptor接口
  • 抛出增强类需要实现org.springframework.aop.ThrowsAdvice接口

201604061031

  • 引入增强类,扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor

201604061035

以上定义了一个引入增强类,扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor类,同时也实现了新定义的Apology接口。在类中首先覆盖了父类的invoke()方法,然后实现了Apology接口的方法

20160406103701

需要注意proxyTargetClass属性,它表明是否代理目标类,默认为false,也就是代理接口了,此时Spring就用JDK动态代理。如果为true,那么Spring就用CGLib动态代理

201604061038

saySorry()方法是可以被greetingImpl对象来直接调用的,只需将其强制转换为该接口即可

Spring AOP :切面

通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到ProxyFactory中,从而生成代理

Advisor(切面)封装了Advice(增强)与Pointcut(切点 )

201604061048
201604061049

注意以上代理对象的配置中的interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。

需要强调的是,这里的切点表达式是基于正则表达式的。
示例中的aop.demo.GreetingImpl.good.*表达式后面的.*表示匹配所有字符,翻译过来就是“匹配 aop.demo.GreetingImpl 类中以 good 开头的方法”

除了RegexpMethodPointcutAdvisor以外,在Spring AOP中还提供了几个切面类,比如:

  • DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)

  • NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面

  • StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面

Spring AOP:自动代理

  • 扫描Bean名称

201604061054

以上使用BeanNameAutoProxyCreator只为后缀为ImplBean生成代理。

需要注意的是,这个地方我们不能定义代理接口,也就是interfaces属性,因为我们根本就不知道这些Bean到底实现了多少接口。此时不能代理接口,而只能代理类。

所以这里提供了一个新的配置项,它就是optimize。若为true时,则可对代理生成策略进行优化(默认是false的)。也就是说,如果该类有接口,就代理接口(使用JDK动态代理);
如果没有接口,就代理类(使用CGLib动态代理)。而并非像之前使用的proxyTargetClass属性那样,强制代理类,而不考虑代理接口的方式。可见Spring AOP确实为我们提供了很多很好地服务

CGLib创建代理的速度比较慢,但创建代理后运行的速度却非常快,而JDK动态代理正好相反。如果在运行的时候不断地用CGLib去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用CGLib去创建代理,并放入SpringApplicationContext中以备后用。

  • 扫描切面配置

201604061057

这里无需再配置代理了,因为代理将会由DefaultAdvisorAutoProxyCreator自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。

Spring + AspectJ

  • 基于注解:通过 AspectJ execution 表达式拦截方法

201604061103

类上面标注的@Aspect注解,这表明该类是一个Aspect(其实就是Advisor)。

该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注@Around注解,在注解中使用了AspectJ切点表达式。方法的参数中包括一个ProceedingJoinPoint对象,它在AOP中称为Joinpoint(连接点),可以通过该对象获取方法的任何信息

execution(* aop.demo.GreetingImpl.*(..))

execution():表示拦截方法,括号中可定义需要匹配的规则。

第一个“*”:表示方法的返回值是任意的。

第二个“*”:表示匹配该类中所有的方法。

(..):表示方法的参数是任意的。 

配置<aop:aspectj-autoproxy proxy-target-class="true"/>

  • 基于注解:通过 AspectJ @annotation 表达式拦截方法 201604061109

201604061111

除了@Around注解外,其实还有几个相关的注解,稍微归纳一下吧:

@Before:前置增强

@After:后置增强

@Around:环绕增强

@AfterThrowing:抛出增强

@DeclareParents:引入增强

除了使用@Aspect注解来定义切面类以外,Spring AOP也提供了基于配置的方式来定义切面类:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans ...">
    <bean id="greetingImpl" class="aop.demo.GreetingImpl"/>
    <bean id="greetingAspect" class="aop.demo.GreetingAspect"/>
    <aop:config>
        <aop:aspect ref="greetingAspect">
            <aop:around method="around" pointcut="execution(* aop.demo.GreetingImpl.*(..))"/>
        </aop:aspect>
    </aop:config>
    </beans>

使用<aop:config>元素来进行AOP配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。

201604061419

Reference

http://blog.csdn.net/giserstone/article/details/17199755