SpringAOP高频知识梳理

Spring AOP原理深层解析

前言

IOC和AOP是Spring的两个重要组成部分,IOC之前也经过分析(点击跳转)可以抽象认为这是一个容器,那AOP又是什么东西呢?

AOP是Aspect-Oriented Programming(面向方面编程或者面向切面)的简称。它可以看成是OOP(面向对象编程)的一种延续。简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强。

AOP的知识点特别多,这里简单整理一些比较重要的知识点概念。如果需要深度学习下去,可以参考源码分析:推荐阅读

什么是AOP?

(这里主要是摘录https://gyl-coder.top/spring/spring-ioc-aop/)

下面我们先看一个 OOP 的例子。

例如:现有三个类,HorsePigDog,这三个类中都有 eat 和 run 两个方法。

通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,HorsePigDog通过继承Animal类即可自动获得 eat()run() 方法。这样将会少些很多重复的代码。

OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。

/**
 * 动物父类
 */
public class Animal {

    /** 身高 */
    private String height;

    /** 体重 */
    private double weight;

    public void eat() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can eat...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }

    public void run() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can run...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }
}

这部分重复的代码,一般统称为 横切逻辑代码

横切逻辑代码存在的问题:

  • 代码重复问题
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护

AOP 就是用来解决这些问题的

AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离

代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。

AOP 解决了什么问题

通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

AOP 为什么叫面向切面编程

:指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑

:横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念

重要术语

连接点(Join point):

  • 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~

切点(Poincut):

  • 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点

增强/通知(Advice):

  • 表示添加到切点的一段逻辑代码,并定位连接点的方位信息

    • 简单来说就定义了是干什么的,具体是在哪干
    • Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
  • 五种Advice类型如下:

    • 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。
    • 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning 关注使用它。
    • 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。
    • 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。
    • 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。

织入(Weaving):

  • 增强/通知添加到目标类的具体连接点上的过程。

引入/引介(Introduction):

  • 引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

切面(Aspect):

  • 切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

底层实现

我们上面介绍了AOP的一系列相关术语,而这些术语其实也就是为了加深我们对AOP的面向切面的认识。AOP的底层其实是通过动态代理来实现的,其实如果学过设计模式其实看着AOP的思想也能大概猜出来了。将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能

在SpringAOP中,我们底层的通过两种动态代理来实现的,分别是JDK动态代理和CGLib动态代理。

JDK动态代理

关于JDK的动态代理代码演示可以看一下我的这一篇,顺便也可以了解一下动态代理模式(点击跳转

JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了~~

  • CGLib代理其生成的动态代理对象是目标类的子类
  • Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。

那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:

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

原因:

  • JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
  • 如果是单例的代理,推荐使用CGLib

CGLib动态代理

字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。

因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就 必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖 。

现在演示一下如何使用CGLIB动态代理实现开头的情景,CGLIB动态代理不要求被代理类实现接口,先写一个被代理类。

public class MonkeyOperation {
    public void put() {
        System.out.println("放入猴子...");
    }
 
    public void get() {
        System.out.println("拿出猴子...");
    }
}

在写一个类实现MethodInterceptor接口,并在接口方法intercept()里对被代理对象的方法做增强,并编写生成代理对象的方法

public class FridgeCGLibProxy implements MethodInterceptor {
 
    public String name="hahaha";
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        openDoor();//调用被代理方法做一些操作
        Object result = methodProxy.invokeSuper(proxy,args);//执行被代理对象的方法,如果方法有返回值则赋值给result
        closeDoor();//调用被代理方法后做一些操作
        return result;
    }
    private void openDoor(){
        System.out.println("打开冰箱...");
    }
    private void closeDoor(){
        System.out.println("关闭冰箱...");
    }
    public Object getProxy(Class cls){//参数为被代理的类对象
        Enhancer enhancer = new Enhancer();//创建增强器,用来创建动态代理类
        enhancer.setSuperclass(cls);//设置父类,即被代理的类对象
        enhancer.setCallback(this);//设置回调,指定为当前对象
        return enhancer.create();//返回生成的代理类
    }
}

测试及结果:

  public static void main(String args[]) {
      MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
      monkeyOperation.put();
      monkeyOperation.get();
  }

应用场景

通过使用SpringAOP,我们可以将我们与业务无关的代码,分割出来。具体的应用场景主要是有这些:

  1. 权限控制
  2. 缓存控制
  3. 事务控制
  4. 审计日志
  5. 性能监控
  6. 分布式追踪
  7. 异常处理

如何在这些场景去使用就需要根据情况具体实践了。可以多看看实战书籍!

参考资料

SpringAOP就是这么简单啦

https://gyl-coder.top/spring/spring-ioc-aop/

Spring中的AOP原理

《Spring技术内幕:深入解析Spring架构与原理》

原文地址:https://www.cnblogs.com/CryFace/p/13646924.html