Spring AOP 详解

5,048 阅读5分钟

前言

之前我们有提到 Spring 中的 AOP 切面编程, 其实 AOP 在实际开发中实用性很强, 掌握了 AOP 你会发现开启了新世界的大门, 在学习 AOP 之前, 需要先掌握代理模式, 这个在本篇不做讲解

为什么要使用 Spring AOP ?

我们都知道 Spring AOP 又被称为面向切面编程, 我们只听过 OOP(面向对象编程) 怎么去理解面向切面编程呢 ?

来看下面这段伪代码 :

public Student queryStudent(int studentId){
​
        // 记录方法开始时间
        long startTime = System.currentTimeMillis();
​
        // 查询学生信息
        Student student = studentDao.selectStudentInfo(studentId);
​
        // 记录操作日志
        operationLog.add();
        
        // 记录方法结束时间
        long endTime = System.currentTimeMillis();
        // 计算方法执行时间
        log.info("执行时间: " + (endTime - startTime));
​
        return student;
  }

我们可以看到, 除了查询学生信息是业务代码以外, 其它的所有步骤都不属于业务代码 .

在平时工作中如果遇到这种代码的解决方案无非两种 :

  • 抽取方法
  • 抽取类

这两种方法我们称为 : 纵向抽取

回顾平时开发的固定模式, Controller -> Service -> Dao, 在这种顺序执行, 纵向延伸的执行逻辑里面不管怎么进行上面的两种封装, 依旧还是会出现重复的代码, 以上面为例 : 即便将记录方法时间与记录用户操作记录封装为一个方法, 那么这个方法依旧要依附与我们主体业务类的方法逻辑中

结论就是 : 纵向并不能解决此类问题

此时就要引出本章的 AOP 理念 : 将分散在各个业务逻辑代码中的相同代码通过横向切割的方式抽取到独立模块中

切面示意图.png

将不属于业务逻辑的代码, 抽取到切面方法中执行, 可以称它为横向抽取

Spring AOP 原理

如果之前接触过代理模式我就发现, 上面的横向抽取是可以通过代理模式来完成的, 通过一个代理类来增强对象的行为, 而 Spring AOP 的底层原理就是 动态代理

在 Java 中动态代理的实现方式有两种 :

  • JDK 动态代理
  • cglib 动态代理

在 Spring AOP 中默认使用的是 JDK 动态代理, 而在代理类没有接口时会采用 cglib 代理, cglib 代理生成的动态代理对象是目标的子类 .

关于 JDK 代理与 cglib 代理的选择, 这里采用《精通Spring4.x 企业应用开发实战》给出的建议 :

  • 如果是单例的最好使用 cglib 代理,如果是多例的最好使用 JDK 代理

理由如下 :

  • JDK 在创建代理对象时性能要高于 cglib 代理, 但是生成代理对象的运行性能却不如 cglib
  • 如果是单例, 则选用 cglib

我们可以通过上述知识点总结出 Spring AOP : 将相同逻辑的重复代码横向抽取, 使用动态代理将重复代码重新融合到方法中, 实现和原方法一样的功能, 这样我们就可以在写代码时主干逻辑只存在业务代码, 而不需要关心与业务无关的代码 .

Spring AOP 的使用

Spring 存在至今为止提供了3种 AOP 的支持 :

  • 基于代理的 Spring AOP : 需要实现接口, 手动创建代理
  • POJO 切面 : 使用 XML 配置, aop 命名空间
  • @Aspect 注解驱动切面 : 使用注解, 简介明了

@Aspect 方式实现 AOP

这里主要介绍 @Aspect 方式实现 AOP 的方法, 我们了解一下常用注解 :

  • @Aspect : 声明这是一个切面类, 需要同 @Component 一起使用, 表明交由 Spring 管理

  • @Pointcut : 定义一个切点, 存在两种表达方式 :

    • execution() 匹配方法
    • annotation() 匹配注解
  • @Around : 增强处理, 表示环绕增强, 可以任意执行

  • @Before : 表示在切点方法前执行

  • @After : 表示在方法后执行

  • @AfterReturning : 与 @After 类似, 但可以捕获切点方法返回值进行增强处理

  • @AfterThrowing : 在方法抛出异常时执行

这里我们以环切为例, 写一个计算方法执行时间的 AOP 例子 :

@Aspect
@Component
public class TimeAspect {
​
    private static Logger log = Logger.getLogger(TimeAspect.class);
​
    /**
     * 定义切点 切入com.test.service包下,携带@RequestMapping注解的方法
     * @author liuhanchao
     */
    @Pointcut("execution( * com.test.service..*.*(..)) &&(@annotation(org.springframework.web.bind.annotation.RequestMapping))")
    public void doPointcut() {}
​
    /**
     * 环切计算方法执行时间
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        long startTime = System.currentTimeMillis();
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        Object ret = ret=proceedingJoinPoint.proceed();
        long endTime = System.currentTimeMillis();
        log.info(method.getName()+" succ , time consuming: " + (endTime - startTime) + " ms");
        return ret;
    }
}

总结

Spring AOP 中有很多知识点, 本篇文章主要从为什么使用, 实现原理, 如何使用 ? 三个方面进行了讲解, 并没有过于深入底层源码, 关于 Spring AOP 其实我们只需要知道在什么情况下应该使用, 还有核心知识点就可以了

下面进行总结 :

  • Spring AOP 底层实现为动态代理, 分为 JDK 动态代理cglib 动态代理, Spring AOP 默认使用 JDK 代理, 在代理类没有接口时使用 cglib 代理
  • 单例选择 cglib 动态代理, 原因是 cglib 代理对象运行速度比 JDK 代理对象快
  • Spinrg AOP 基于动态代理, 所以它只能对方法进行拦截

常见的 AOP 使用场景 :

  • 方法执行时间的统计
  • 日志
  • 权限
  • ......

结尾

关于 Spring 后续还要写的有 循环依赖问题 和 SpringBoot 的自动装配问题, 有点想不出来写啥... 兄弟们有什么想看的可以在下方留言, 我来研究研究然后一起交流

关注公众号 logerJava 更多知识分享等你来看