spring 4.0 AOP (使用AspectJ的注解方式 的aop实现)简单实例

  • AspectJ:Java 社区里最完整最流行的 AOP 框架. spring aop 配合使用aspectj(AOP框架)实现我们所需的aop功能
  • 在 Spring 中启用 AspectJ 注解支持 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

  • AspectJ 支持 5 种类型的通知注解:

    • @Before: 前置通知, 在方法执行之前执行
    • @After: 后置通知, 在方法执行之后执行
    • @AfterRunning: 返回通知, 在方法成功执行返回结果之后执行
    • @AfterThrowing: 异常通知, 在方法抛出异常之后
    • @Around: 环绕通知, 围绕着方法执行
    有两种方法实现aop功能,一个是完全基于javaconfig配置类注解实现,另一种是基于xml
  • 关于Spring AOP的AspectJ切点,最重要的一点就是Spring仅支持AspectJ切点指示器(pointcut designator)的一个子集。让我们回顾下,Spring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。表4.1列出了Spring AOP所支持的AspectJ切点指示器。
  • spring 4.0 AOP (使用AspectJ的注解方式 的aop实现)简单实例
  • 基于JavaConfig配置实例 如下:

  • 目录:

  • spring 4.0 AOP (使用AspectJ的注解方式 的aop实现)简单实例
    JavaConfig配置类:

    1. package aopbean;  
    2.   
    3. import org.springframework.context.annotation.ComponentScan;  
    4. import org.springframework.context.annotation.Configuration;  
    5. import org.springframework.context.annotation.EnableAspectJAutoProxy;  
    6. @Configuration  
    7. //开启AspectJ 自动代理模式,如果不填proxyTargetClass=true,默认为false,  
    8. //即使用jdk默认代理模式,AspectJ代理模式是CGLIB代理模式  
    9. //如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP   
    10. //如果目标对象实现了接口,可以强制使用CGLIB实现AOP (此例子我们就是强制使用cglib实现aop)  
    11. //如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换  
    12. @EnableAspectJAutoProxy(proxyTargetClass=true)  
    13. @ComponentScan  
    14. public class JavaConfig {  
    15.   
    16. }  
    Performance接口:
    1. package aopbean;  
    2.   
    3. //目标接口  
    4. public interface Performance {  
    5.       
    6.     public void perform();  
    7.   
    8. }  

    dance实现类:
    1. package aopbean;  
    2.   
    3. import org.springframework.stereotype.Component;  
    4.   
    5. @Component  
    6. public class Dance implements Performance {  
    7.   
    8.     public void perform() {  
    9.         System.out.println("开始看跳舞");  
    10.   
    11.     }  
    12.   
    13. }  

    切面类Audience:
    1. package aopbean;  
    2.   
    3. import org.aspectj.lang.annotation.After;  
    4. import org.aspectj.lang.annotation.Aspect;  
    5. import org.aspectj.lang.annotation.Before;  
    6. import org.aspectj.lang.annotation.Pointcut;  
    7. import org.springframework.stereotype.Component;  
    8.   
    9. @Aspect//声明切面,没有声明的话不会起作用  
    10. @Component  
    11. public class Audience {  
    12.     public Audience(){  
    13.           
    14.     }  
    15.       
    16.     //声明切入点  
    17.     //第一个*表示 方法  返回值(例如public int)  
    18.     //第二个* 表示方法的全限定名(即包名+类名)  
    19.     //perform表示目标方法参数括号两个.表示任意类型参数  
    20.     //方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,  
    21.     //我们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么  
    22.     //execution表示执行的时候触发  
    23.     @Pointcut("execution(* *.perform(..))")  
    24.     public void dancepoint(){  
    25.         //该方法就是一个标识方法,为pointcut提供一个依附的地方  
    26.     }  
    27.       
    28.     @Before("dancepoint()")  
    29.     public void beforeDance(){  
    30.         System.out.println("找座位。。。。");  
    31.     }  
    32.     @After("dancepoint()")  
    33.     public void afterDance(){  
    34.         System.out.println("看完回家");  
    35.     }  
    36.       
    37.   
    38. }  
    测试类:
    1. package aopbean;  
    2.   
    3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
    4.   
    5. public class Test {  
    6.   
    7.       
    8.         public static void main(String[] args) {  
    9.             AnnotationConfigApplicationContext ac =  
    10.                     new AnnotationConfigApplicationContext(JavaConfig.class);  
    11.          Dance d = ac.getBean("dance",Dance.class);  
    12.          d.perform();  
    13.   
    14.         }  
    15.   
    16. }  

    输出结果:

    找座位。。。。
    开始看跳舞
    看完回家
  • 环绕通知 round
    1. @Around("dancepoint()")  
    2. public void roudDance(ProceedingJoinPoint jp) throws Throwable{  
    3.     System.out.println("找座位");  
    4.     jp.proceed();  
    5.     System.out.println("回家");  
    6.       
    7. }  
    输出:找座位
    开始看跳舞
    回家
  • 可以看到,这个通知所达到的效果与之前的前置通知和后置通知是一样的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同的通知方法里面。

    关于这个新的通知方法,你首先注意到的可能是它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPointproceed()方法。

    需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要的效果,但更多的情况是你希望在某个点上执行被通知的方法。

    有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试。