Bootstrap

Spring Boot 整合 AOP

前言

在 Spring 框架中有一个 AOP 子框架,自己创建了一套独有的表达式语言,通过这些表达式语言,可以轻松控制业务类中某些方法横切或植入新功能,以达到低耦合的效果。Spring AOP 底层用的就是动态代理,不用写动态代理代码。

目标类有实现业务接口就默认用jdk动态代理,目标类没有实现业务接口就用cglib,有业务接口也可以通过指定配置项来使用cglib。

在 Spring Boot 中加入 AOP 是不需要在启动类加任何注解的,因为在AOP的默认配置属性中,spring.aop.auto 属性默认是开启的,也就是说只要引入了 AOP 依赖后,默认已经增加了 @EnableAspectJAutoProxy。

AOP 的若干名词

  • 切面:就是新的业务模型,也叫切面类或者新功能类,它的实例化对象为切面对象。以注解@Aspect的形式放在类上方,声明一个切面。
  • 目标对象:就是旧的业务模型实例化的对象,被一个或者多个切面所通知的对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个代理对象。
  • 连接点:用代理对象调用目标方法的那句话的位置就叫连接点,但前提一定用代理对象调用才是连接点。
  • 切点:其实就是筛选出的连接点,匹配连接点的断言,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。
  • 通知:需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。包括如下五个方面:
@Before:在切点之前执行
@After:在切点方法之后执行
@AfterReturning:切点方法返回后执行
@AfterThrowing:切点方法抛异常执行

AOP的体系可以梳理为下图:

在这里插入图片描述

Spring Boot 如何整合 AOP

在实际开发中对于横向公共的逻辑需要抽取出来,这时候就需要使用AOP,比如日志的记录、权限的验证等等,这些功能都可以用注解轻松的完成。

  1. 添加依赖
<!--springboot集成Aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 定义需要的切面
package com.example.canal.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Component // 放到容器中
@Aspect // 标识为切面
@Slf4j // 引入 lombok 依赖,方便使用log
public class ControllerAspect {

    /**
     * (* com.example.canal.*.*(..))
     * 第一个 * 代表所有的方法
     * 第二个 * 前面 com.example.canal.* 下面的所有类
     * 第三个 * 代表以及子类 最后面括号里面的
     * .. 代表所有参数
     *
     * 定义切入点,切入点为 com.example.canal 下的所有函数
     */
    @Pointcut("execution(* com.example.canal.yang.*.*(..))")
    private void yangCut() {}

    // 方法用 execution 指定,注解用 @annotation 指定,注解的类型是 @interface,算是一种特殊的接口
    // @Pointcut("@annotation(com.chy.mall.annotation.UploadObs)")

    /**
     * 前置通知,在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
     */
    @Before("yangCut()")
    public void before(JoinPoint joinPoint) {
        // 接收到请求,RequestContextHolder来获取请求信息,Session信息
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        log.info("URL : " + request.getRequestURL().toString());
        log.info("HTTP_METHOD : " + request.getMethod());
        log.info("IP : " + request.getRemoteAddr());
        // getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
            + joinPoint.getSignature().getName());
        // getArgs() 获取传入目标方法的参数对象
        log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));

        System.out.println("正在执行前置通知");
    }

    /**
     * 后置通知,在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
     */
    @After("yangCut()")
    public void after() {
        System.out.println("正在执行后置通知");
    }

    /**
     * 后置返回通知,可传递目标方法的返回值,在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 obj 目标方法的返回值
     */
    @AfterReturning(value = "yangCut()", returning = "obj")
    public void afterReturning(Object obj) {
        System.out.println("目标方法的返回值是:" + obj);
    }

    /**
     * 后置异常通知,可传递异常对象,在连接点抛出异常后执行。
     */
    @AfterThrowing(value = "yangCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        log.info(joinPoint.getSignature().getName());
        System.out.println("异常信息:" + e.getMessage());
    }

    /**
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    // @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 前增强
        System.out.println("正在执行前置通知");
        // 调用目标方法
        Object object = proceedingJoinPoint.proceed();
        // 后增强
        System.out.println("正在执行后置通知");
        // 返回目标方法的返回值
        return object;
    }
}
;