AOP 即面向切面编程(Aspect Oriented Program),可以说是面向对象编程的补充,它提供一种“横切”的技术,将影响了多个类的公共行为封装到一个可重用模块,并将其命名为切面(Aspect),AOP 把软件系统分为两个部分:核心关注点和横切关注点,业务处理的主要流程就是核心关注点,而横切关注点就是核心关注点里的一些公共行为,比如权限认证、日志、事物等

1. 术语介绍

  • 切面(Aspect):切入点和通知的结合
  • 连接点(Joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切入点(Pointcut):对连接点进行拦截的定义
  • 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
  • 目标对象(Target Object):被一个或者多个切面所通知的对象
  • 织入(Weave):将切面应用到目标对象并导致代理对象创建的过程
  • 引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
1.1 通知的类型
名称说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来

2. 实现原理

Spring AOP 是通过动态代理实现的,通过动态代理,可以对被代理对象的方法进行增强
主要用到了两种动态代理技术:JDK 动态代理 和 CGLIB 库
其中 JDK 动态代理只能对实现了接口的类生成代理,CGLIB 则是针对类实现代理,主要是对指定的类生成一个子类,重写其中的方法

3. 切入点表达式

3.1 execute
execution(权限修饰符 返回值类型 类路径(方法名) 抛出的异常类型)

// 拦截任意公共方法
execution(public * *(..))

// 拦截以 set 开头的任意方法
execution(* set*(..))

// 拦截 service 包中以 public 修饰的任意方法
execution(public * ink.ckx.test.*.service.*.*(..))

// 拦截 test 包及其子包下的任意方法
execution(* ink.ckx.test..*.*(..))
3.2 within
// 拦截包中任意方法,不包含子包中的方法
within(ink.ckx.service.*)

// 拦截包或者子包中定义的方法
within(ink.ckx.service..*)
3.3 this
// 拦截 AService 所有的子类的所有外部调用方法
this(ink.ckx.Aservice*)
3.4 target
// 拦截 AServiceImpl 下的所有方法
target(ink.ckx.impl.AServiceImpl)
3.5 args
// 拦截任何不带参数的方法
args()

// 拦截带任意参数的方法
args(…)

// 拦截任何只带一个参数,而且这个参数的类型是 String 的方法
args(java.lang.String)

// 拦截带任意个参数,但是第一个参数的类型是 String 的方法
args(java.lang.String,…)
3.6 @target
// 拦截类上有指定注解的所有外部调用方法
@target(ink.ckx.test.Annotation1)
3.7 @within
// 拦截类上具有指定注解的的所有方法
@within(ink.ckx.test.Annotation1)
3.8 @annotation
// 拦截具有指定注解的方法
@annotation(ink.ckx.test.Annotation1)
3.9 @args
// 拦截一个参数的方法,并且这个参数的类型有指定的注解
@args(ink.ckx.test.Annotation1)

// 拦截两个参数的方法,且两个参数的类型上都有指定的注解
@args(ink.ckx.test.Annotation1,ink.ckx.test.Annotation2)

// 拦截多个参数的方法,且第一个参数类型上有指定的注解
@args(ink.ckx.test.Annotation1,..)

4. 使用

4.1 依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.2 示例
@Slf4j
@Component
@Aspect
public class TestAspect {

    @Pointcut("execution(public * ink.ckx.test.*.service.*.*(..))")
    public void aspect() {
    }

    @Before("aspect()")
    public void before(JoinPoint joinPoint) {
        log.info("before:", joinPoint);
    }

    @After("aspect()")
    public void after(JoinPoint joinPoint) {
        log.info("after:", joinPoint);
    }

    @Around("aspect()")
    public void around(JoinPoint joinPoint) {
        long start = System.currentTimeMillis();
        try {
            ((ProceedingJoinPoint) joinPoint).proceed();
            long end = System.currentTimeMillis();
            log.info("around:{},Use time:{} ms", joinPoint, (end - start));
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("around:{},Use time:{} ms,with exception:{}", joinPoint, (end - start), e.getMessage());
        }
    }

    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint) {
        log.info("afterReturn:{}", joinPoint);
    }

    @AfterThrowing(pointcut = "aspect()", throwing = "ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex) {
        log.info("afterThrow:{},with exception:{}", joinPoint, ex.getMessage());
    }
}

Q.E.D.


盛年不重来,一日难再晨。