在Spring中使用AspectJ实现AOP

2019-08-09 00:00:00 spring aop aspectj

 

在Spring中,最常用的AOP框架是AspectJ,使用AspectJ实现AOP有2种方式:

  • 基于XML的声明式AspectJ

  • 基于注解的声明式AspectJ

 

 

基于XML的声明式AspectJ

1、在项目中添加包spring-aspects.jar(spring自带)、aspectjweaver.jar(需要自己下载添加)

 

 2、新建包user,包下新建类User

1 public class User{
2     public void addUser(){
3         System.out.println("正在添加用户");
4         System.out.println("添加用户成功");
5     }
6 }

User可实现接口。

 

 

3、新建包my_aspect,包下新建切面类MyAspect。注意是新建Class,不是新建Aspect。

 1 public class MyAspect {
 2     //前置通知
 3     public void myBefore(){
 4         System.out.println("正在检查权限");
 5         System.out.println("权限已够");
 6     }
 7 
 8     //后置通知
 9     public void myAfterReturning(){
10         System.out.println("正在记录日志");
11         System.out.println("日志记录完毕");
12     }
13 
14     //环绕通知,同时在前后增强。
15     public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
16         myBefore();
17         Object object=proceedingJoinPoint.proceed();
18         myAfterReturning();
19         return object;
20     }
21 
22     //异常通知
23     public void myAfterThrowing(Throwable e){
24         System.out.println("出错了:"+e.getMessage());
25     }
26 
27     //最终通知
28     public void myAfter(){
29         System.out.println("正在释放资源");
30         System.out.println("资源释放完毕");
31     }
32 }

 根据需要,写通知。函数名是自定义的,但不能使用aspectj的关键字。

可使用形参 JoinPoint joinPoint ,通过 joinPoint.getSignature().getName() 获取增强的方法名。

 

 

4、在xml中配置

<!--目标类-->
    <bean id="user" class="user.User" />
    
    <!--切面类-->
    <bean id="myAspect" class="my_aspect.MyAspect" />
    
    <!--AOP配置-->
    <aop:config>
        <!--配置切面,一个aspect配置一个切面-->
        <aop:aspect ref="myAspect">
            <!--配置全局切入点-->
            <aop:pointcut  id="myPointCut" expression="execution(* user.*.*(..))" />
            <!--配置要使用的通知-->
            <aop:before method="myBefore" pointcut-ref="myPointCut" /> <aop:after-returning method="myAfterReturning" pointcut="execution(void user.User.addUser())" />
        </aop:aspect>
    </aop:config>

 

<aop:pointcut />配置的是全局切入点(所有通知都可引用),可在通知中使用pointcut-ref属性引用。也可以在单个通知中使用pointcut属性配置自己的切入点。

method属性指定这个通知对应的切面类中的方法。如果方法使用了参数JoinPoint(切入点),不用额外传参,pointcut/point-ref传递的就是切入点。

切入点指的就是目标方法。

 

 

  • 前置通知
    <aop:before method="myBefore" pointcut-ref="myPointCut" />

 

 

  • 后置通知
   <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" />

后置通知可使用returning属性指定指定一个参数,这个参数表示目标方法的返回值,会被传递给后置通知的方法。returning属性只有后置通知能使用。

  <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />

 

 

  • 环绕通知(前、后增强)
  <aop:around method="myAround" pointcut-ref="myPointCut" />

环绕方法的参数 ProceedingJoinPoint proceedingJoinPoint 是JoinPoint的子类,不用额外传参。

如果同时使用前置/后置通知、环绕通知,则先执行环绕前,再执行前置(如果有),执行目标方法,执行后置(如果有),然后执行环绕后。

  • 异常通知
    <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>

异常方法需要一个参数 Throwable e ,需要用throwing属性传递参数。

异常通知在目标方法发生异常、抛出异常时自动执行,throwing表示的就是目标方法抛出的异常。

目标方法发生异常时,会先执行异常方法,然后在控制台打印错误信息。

 

 

  • 最终通知
    <aop:after method="myAfter" pointcut-ref="myPointCut" />

最终通知是在目标方法执行后、后置方法执行后/环绕方法执行后(如果有),才执行的。

如果之前的代码(目标方法)发生异常或者被异常中止,后置通知以及环绕通知的后半部分是不会执行的。

最终通知,就算前面代码发生异常、被异常中止,也会执行,除非退出JVM。所以常在最终方法中做一些断开连接、关闭资源的操作。

 

 

 

5、新建包test,包下新建测试类Test

1 public class Test {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
4         User user=applicationContext.getBean("user", User.class);
5         user.addUser();
6     }
7 }

 

 

 

 

 

基于注解的声明式AspectJ

基于代理类的AOP很繁琐,基于xml的声明式AspectJ便捷很多,但如果要配置的切面很多,xml文件会很臃肿。基于注解的声明式AspectJ可解决此问题。

基于注解的声明式AspectJ是最常用的,简便、xml文件也比较简洁。

 

1、在项目中添加包spring-aspects.jar(spring自带)、aspectjweaver.jar(需要自己下载添加)

 

2、新建包user,包下新建类User(可实现接口)

1 @Repository("user") 2 public class User{
3     public void addUser(){
4         System.out.println("正在添加用户");
5         System.out.println("添加用户成功");
6     }
7 }

原本要在xml中配置目标类的Bean,这里使用注解自动装配。@Repository(“user”)是Dao层的注解,因为添加用户一般要操作数据库,这里只是写了一个模拟。

我们显式指定Bean的id/name为user,其实@Repository默认id/name就是类名的camel写法。

 

 

3、新建包my_aspect,包下新建切面类MyAspect

 1 @Aspect 2 @Component  3 public class MyAspect {
 4 //全局切入点 5 @Pointcut("execution(void user.User.*())") 6 private void myPointCut(){}  7 
 8     //前置通知
 9     @Before("myPointCut()") 10     public void myBefore(){
11         System.out.println("正在检查权限");
12         System.out.println("权限已够");
13     }
14 
15     //后置通知
16     @AfterReturning("execution(void user.User.*())") 17     public void myAfterReturning(){
18         System.out.println("正在记录日志");
19         System.out.println("日志记录完毕");
20     }
21 
22     //环绕通知,同时在前后增强。
23     public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
24         myBefore();
25         Object object=proceedingJoinPoint.proceed();
26         myAfterReturning();
27         return object;
28     }
29 
30     //异常通知
31     public void myAfterThrowing(Throwable e){
32         System.out.println("出错了:"+e.getMessage());
33     }
34 
35     //最终通知
36     public void myAfter(){
37         System.out.println("正在释放资源");
38         System.out.println("资源释放完毕");
39 
40     }
41 }

 

原本要在xm中配置切面Bean,这里使用@Aspect(使用aspectj配置,相当于<aop:congif>元素)、 @Component(配置为Bean,相当于<bean>元素)  2个注解配置。

 

需要使用注解把要使用的方法标注为对应的通知方法,需要指定切入点。

 

可先配置全局切入点:

    //全局切入点
    @Pointcut("execution(void user.User.*())")
    private void myPointCut(){}

然后在注解中通过   “方法名()”    引用:

    //前置通知
    @Before(value = "myPointCut()")

如果只配置切入点这一个参数,可简写:

    //前置通知
    @Before("myPointCut()")

 

 

也可以自行配置:

     //后置通知
    @AfterReturning("execution(void user.User.*())")

 

 

异常通知比较特殊,需要传递一个异常参数:

    //异常通知
    @AfterThrowing(value = "myPointCut()",throwing = "e")

 

只有使用了注解标注的方法才会作为通知自动调用。

 

其实就是把xml中的配置转换为相应的注解配置。

 

 

 

4、在xml中配置

    <!--启用Spring注解,指定使用了注解的包,有多个包时逗号分隔-->
    <context:component-scan base-package="user,my_aspect" />

    <!--启用AspectJ的注解-->
    <aop:aspectj-autoproxy />

 

 

5、新建包test,包下新建测试类Test

1 public class Test {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
4         User user=applicationContext.getBean("user", User.class);
5         user.addUser();
6     }
7 }

 

相关文章