详细分析Spring关键之一的Aop模块

1.Aop是什么

我们都知道Spring中最关键的两大特性:IOC和AOP两者相辅相成,成就现在的Spring框架;

AOP叫面向切面编程,帮助我们在很多切入点进行增强、扩充操作,如果是传统我们就需要在所有类开头加上日志开始,末尾加上日志结束,这样不仅编程效率低下,浪费时间,代码冗余,维护也麻烦,万一有需求换方式记录日志,可不得改死;

AOP我们可以使用在很多地方,如日志模块,权限控制,事务处理等;

AOP的具体实现有两种方式

  1. JDK动态代理 (需要被代理类实现一个接口,本质是创建一个代理类也实现该接口,然后对该方法进行增强,扩展)
  2. CGLib字节码处理 (直接对编译好的字节码进行修改,修改对应的方法,本质直接继承该被代理类,所以该类不能设置为final)

2.Aop使用方式

在Spring中要实现aop通常我们会定义一个Aspect类,他使用的是org.aspectj.lang包下的类来帮助我们引入aop模块;

具体使用方式: 1.定义一个Aspect类 添加@Aspect注解、添加@Component注解将该类纳入spring容器; 2.定义切点 使用@PointCut注解定义在方法上,并标明想切入的点(方法) 3.对该切点进行增强 使用@Before、@Around等注解对该方法(@PointCut注解的方法名!!)

具体用法如下


@Aspect
@Component
public class logAspect {
	// 扫描所有controller类的方法
    @Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }
    

    @Before(value = "log()")
    public void before(JoinPoint joinPoint){
        System.out.println("[Aspect] before log method!");
    }

    @Around(value = "log()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }

    @After(value = "log()")
    public void after(){
        System.out.println("[Aspect] after log method!");

    }

    @AfterReturning(value = "log()")
    public void afterReturning(){
        System.out.println("[Aspect] afterReturning log method!");
    }

    @AfterThrowing(value = "log()")
    public void afterThrowing(){
        System.out.println("[Aspect] afterThrowing log method!");
    }

}

3.具体注解作用

1.@Pointcut

使用参数value可以定义切点 @Pointcut(value = "execution(* com.exam.exam.controller..(..))") 扫描所有controller包下所有类中所有返回值的方法

在这里插入图片描述

@Pointcut("@within(com.exam.exam.logaspect.LogAnnotation)") 扫描所有LogAnnotation注解标识的类,这个类以及它的子孙类都能被增强。

within()与execution()的功能类似,两者的区别是,within()定义的连接点的最小范围是类级别的(不是接口),而execution()定义的连接点的最小范围可以精确到方法的入参,因此可以认为execution()涵盖了within()的功能。

@Pointcut("within(com.exam.exam.entity.User)") 扫描该类及其子类,当注入使用的时候对它进行增强

target() 匹配目标类以及子类,目标类以及其子类的所有方法执行都会匹配到 @target:若当前类有注解,则对该类继承、自有、重载的方法有效。若子类无注解,则无效果。 @Annotation 匹配使用了该注解的类

@target和@within区别:两者都用于注解类 1.我们对被@target、@within注解的类中的方法会进行增强,但只有@within子类重写的方法会生效; 2.对于被@target注解的子类,若子类调用父类的方法,而没有重写父类的方法那么就会被增强,但@within不会 3.对于被@within注解的父类,若父类方法子类没有重写,那么子类调用父类的方法,就会被增强,但@target不会 (2,3区别在于一个是@target子类调用父类的方法,一个是子类调用@within父类的方法) 4.@target、@within对子类新增方法都不起作用

2.@Before

用于切面前置动作,在执行方法前增强动作 一般两种用法

  1. 定义@Pointcut方法,对该方法进行前置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

    @Before(value = "log()")
    public void before(JoinPoint joinPoint){
        System.out.println("[Aspect] before log method!");
    }
  1. 直接使用@within、@target等对注解进行切入
    @Before("@target(com.exam.exam.logaspect.LogAnnotation)")
    public void before1(){
    }

3.@After

用于切面后置动作,在执行方法后增强动作

  1. 定义@Pointcut方法,对该方法进行后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

    @After(value = "log()")
    public void After(JoinPoint joinPoint){
        System.out.println("[Aspect] After log method!");
    }
  1. 直接使用@within、@target等对注解进行切入
    @After("@within(com.exam.exam.logaspect.LogAnnotation)")
    public void After(){
    }

4.@Around

用于切面环绕动作,对方法进行环绕操作

  1. 对切面进行环绕,前后操作;
	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }
    
    @Around(value = "log()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }
  1. 直接使用@within、@target等对注解进行切入
	@Around(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }

5.@afterReturning

在方法返回后进行的后置处理

  1. 定义@Pointcut方法,对该方法进行返回后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

	@AfterReturning(value = "log()")
    public void afterReturning(){
        System.out.println("[Aspect] afterReturning log method!");
    }

  1. 直接使用@within、@target等对注解进行切入
	@AfterReturning(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void afterReturning(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] afterReturning log method!");
    }

6.@AfterThrowing

在方法抛出异常后进行的后置处理

  1. 定义@Pointcut方法,对该方法进行返回后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

	@AfterThrowing(value = "log()")
    public void AfterThrowing(){
        System.out.println("[Aspect] AfterThrowing log method!");
    }

  1. 直接使用@within、@target等对注解进行切入
	@AfterThrowing(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void AfterThrowing(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] AfterThrowing log method!");
    }

4.参数ProceedingJoinPoint和JoinPoint

环绕通知(around) ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。

Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。

5.调用顺序

执行流程图:

在这里插入图片描述

1.当一个Aspect的时候

# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
doing test aspect!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!

2.当两个Aspect的时候

# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
[Aspect2] around log before method!
[Aspect2] before log method!
doing test aspect!
[Aspect2] afterReturning log method!
[Aspect2] after log method!
[Aspect2] around log after method!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!

3.当抛出异常的时候

[Aspect] around log before method!
[Aspect] before log method!
[Aspect] afterThrowing log method!
[Aspect] after log method!
2021-03-13 20:52:26.730 ERROR 21632 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: error] with root cause

java.lang.RuntimeException: error

我们可以看到afterReturning没有执行,around-after没有执行 after执行了!

参考:https://www.jianshu.com/p/fb109e03edec

end
  • 作者:Endwas(联系作者)
  • 发表时间:2021-03-14 00:15
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者名字和博客地址
  • 评论

    888
    888
    666
    666
    777  @ 666
    777
    888  @ 777
    888