1、AOP是什么?

Spring的最终目的是简化应用开发。通俗的讲减少重复代码,少写代码达到相同的目的。面向切面编程(AOP, Aspect Oriented Programming)就是一种减重复代码方式。我们都知道JAVA是一门面向对象编程(OOP, Object Oriented Programming)语言,在java中将一个个功能模块抽象成一个个对象。这些对象通过一定的联系完成我们所看到的一个个应用,一个个服务。它的核心就是对象(Object)。

作者:林湾村龙猫

2、Spring Boot使用AOP

pom.xml添加依赖

spring boot aop 使用 java spring boot aop_spring boot aop 使用

编写日志bean

spring boot aop 使用 java spring boot aop_AOP_02

实现日志管理的DAO、Service、Controller

spring boot aop 使用 java spring boot aop_目标对象_03

spring boot aop 使用 java spring boot aop_spring boot aop 使用_04

 

spring boot aop 使用 java spring boot aop_优先级_05

编写AOP的核心类

spring boot aop 使用 java spring boot aop_spring boot aop 使用_06

常用注解说明:

@Aspect注释告诉Spring这是个切面类

@Compoment将转换成Spring容器中的bean或者是代理bean。

@PointCut

这个注解包含两部分,PointCut表达式和PointCut签名。表达式是拿来确定切入点的位置的,说白了就是通过一些规则来确定,哪些方法是要增强的,也就是要拦截哪些方法。

@PointCut(...........)括号里面那些就是表达式。这里的execution是其中的一种匹配方式,还有:


execution: 匹配连接点 within: 某个类里面 this: 指定AOP代理类的类型 target:指定目标对象的类型 args: 指定参数的类型 bean:指定特定的bean名称,可以使用通配符(Spring自带的) @target: 带有指定注解的类型 @args: 指定运行时传的参数带有指定的注解 @within: 匹配使用指定注解的类 @annotation:指定方法所应用的注解


spring boot aop 使用 java spring boot aop_spring boot aop 使用_07

@Before

这个是决定advice在切入点方法的什么地方执行的标签,这个注解的意思是在切入点方法执行之前执行我们定义的advice。

spring boot aop 使用 java spring boot aop_目标对象_08

JoinPoint包含了几个很有用的参数:

  • Object[] getArgs:返回目标方法的参数
  • Signature getSignature:返回目标方法的签名
  • Object getTarget:返回被织入增强处理的目标对象
  • Object getThis:返回AOP框架为目标对象生成的代理对象

@After

这个注解就是在切入的方法运行完之后把我们的advice增强加进去。一样方法中可以添加JoinPoint。

spring boot aop 使用 java spring boot aop_优先级_09

@Around

这个注解可以简单地看作@Before和@After的结合。这个注解和其他的比比较特别,它的方法的参数一定要是ProceedingJoinPoint,这个对象是JoinPoint的子类。我们可以把这个看作是切入点的那个方法的替身,这个proceedingJoinPoint有个proceed()方法,相当于就是那切入点的那个方法执行,简单地说就是让目标方法执行,然后这个方法会返回一个对象,这个对象就是那个切入点所在位置的方法所返回的对象。

除了这个Proceed方法(很重要的方法),其他和那几个注解一样。

 

@AfterReturning

顾名思义,这个注解是在目标方法正常完成后把增强处理织入。这个注解可以指定两个属性(之前的三个注解后面的括号只写一个@PointCut表达式,也就是只有一个属性),一个是和其他注解一样的PointCut表达式,也就是描述该advice在哪个接入点被织入;然后还可以有个returning属性,表明可以在Advice的方法中有目标方法返回值的形参。


@AfterReturning(returning = "returnOb", pointcut = "controllerLog() || uiControllerLog()")
    public void doAfterReturning(JoinPoint joinPoint, Object returnOb) {
        System.out.println("##################### the return of the method is : " + returnOb);
    }


@AfterThrowing

异常抛出增强,在异常抛出后织入的增强。有点像上面的@AfterReturning,这个注解也是有两个属性,pointcut和throwing。

用法也和刚刚的那个returning差不多:


@AfterThrowing(pointcut = "controllerLog() || uiControllerLog()", throwing = "ex") public void doAfterThrowing(JoinPoint joinPoint, Exception ex) { String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex); }


 

最后,再记录一下各个不同的advice的拦截顺序的问题。

情况一,只有一个Aspect类:

  无异常:@Around(proceed()之前的部分) → @Before → 方法执行 → @Around(proceed()之后的部分) → @After → @AfterReturning

  有异常:@Around(proceed(之前的部分)) → @Before → 扔异常ing → @After → @AfterThrowing    (大概是因为方法没有跑完抛了异常,没有正确返回所有@Around的proceed()之后的部分和@AfterReturning两个注解的加强没有能够织入)

 

情况二,同一个方法有多个@Aspect类拦截:

  单个Aspect肯定是和只有一个Aspect的时候的情况是一样的,但不同的Aspect里面的advice的顺序呢??答案是不一定,像是线程一样,没有谁先谁后,除非你给他们分配优先级,同样地,在这里你也可以为@Aspect分配优先级,这样就可以决定谁先谁后了。

优先级有两种方式:

  • 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
  • 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order

不管是哪种,都是order的值越小越先执行:


@Order(5)
@Component
@Aspect
public class Aspect1 {
    // ...
}

@Order(6)
@Component
@Aspect
public class Aspect2 {
    // ...
}


这样Aspect1就永远比Aspect2先执行了。

 

注意点:

  • 如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个 @Before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了 @Order 这个注解,也不行。这点切记。
  • 对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了 @Before这个advice不会被触发。