15. Spring AOP 的实现原理 & 代理模式

目录

[1. 代理模式](#1. 代理模式)

[2. 静态代理](#2. 静态代理)

[3. 动态代理](#3. 动态代理)

[3.1 JDK 动态代理](#3.1 JDK 动态代理)

[3.2 CGLIB 动态代理](#3.2 CGLIB 动态代理)

[4. JDK 动态代理和 CGLIB 动态代理对比](#4. JDK 动态代理和 CGLIB 动态代理对比)

[5. Spring代理选择](#5. Spring代理选择)

[6. Spring AOP 实现原理](#6. Spring AOP 实现原理)

[6.1 织入](#6.1 织入)

[7. JDK 动态代理实现](#7. JDK 动态代理实现)

[8. CGLIB 动态代理实现](#8. CGLIB 动态代理实现)

[9. 总结](#9. 总结)


1. 代理模式

代理模式:为其他对象提供⼀种 代理 以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另⼀个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式分为静态代理动态代理

2. 静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口⼀旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写⼀个代理类)。 实际应用场景非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接⼝、实现类、代理类这些都变成了⼀个个实际的 class 文件

静态代理实现步骤:

  1. 定义⼀个接口及其实现类;
  2. 创建⼀个代理类同样 实现这个接口;
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。

这样,我们就可以通过代理类屏蔽目标对象的访问,并且可以在目标方法执行前后实现其他的功能。接下来,我们来看一下具体是如何实现静态代理的:

  1. 定义接口
  1. 实现接口
  1. 创建代理类并同样实现支付接口
  1. 实际使用

3. 动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中 的。
Spring AOP 的实现依赖了动态代理。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

3.1 JDK 动态代理

在 Java 动态代理机制中 InvocationHandler 接口Proxy 类 是核心。
JDK 动态代理类步骤:

  1. 定义⼀个接口及其实现类;
  2. 自定义 InvocationHandler 并重写 invoke 方法 ,在 invoke 方法中会调用原生方法(被代理类的方法)并自定义⼀些处理逻辑;
    3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
    interfaces,InvocationHandler h) 方法创建代理对象;

定义 JDK 动态代理类:

创建一个代理对象并使用:


Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成⼀个代理对象。

这个方法⼀共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的⼀些接口;
  3. h : 实现了 InvocationHandler 接口的对象;

3.2 CGLIB 动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接口的类。

因此,可以通过 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是⼀个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知 名的开源框架都使用到 CGLIB,例如 Spring 的 AOP 模块中:如果目标对象 实现了接口 ,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理
在 CGLIB 动态代理机制中 MethodInterceptor 接口Enhancer 类 是核心。

CGLIB 动态代理类使用步骤:

  1. 定义⼀个类;
  2. ⾃定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类。

添加依赖:

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项目,使用时需要手动添加相关依赖。

XML 复制代码
<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

自定义 MethodInterceptor(方法拦截器):

创建代理类, 并使用:


自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于拦截增强被代理类的方法

  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法入参
  4. proxy : 用于调用原始方法

4. JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
  2. CGLIB 动态代理是通过生成⼀个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final。

性能: 大 部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

5. Spring代理选择

  1. proxyTargetClass 为 false, 目标实现了接口, 用 jdk 代理
  2. proxyTargetClass 为 false, 目标未实现接口, 用 cglib代理
  3. proxyTargetClass 为 true, 用 cglib 代理

6. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下, 实现了接口的类,使
用 AOP 会基于 JDK 生成代理类;没有实现接口的类,会基于 CGLIB 生成代理类。

6.1 织入

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对
象中。
在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就
    是以这种⽅式织⼊切⾯的。
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
  • 运行期:切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

7. JDK 动态代理实现

java 复制代码
public interface PayService {
    void pay();
}
java 复制代码
public class AliPayService implements PayService{
    @Override
    public void pay(){
        System.out.println("ali pay...");
    }
}
java 复制代码
public class PayServiceJDKInvocationHandler implements InvocationHandler {
    // 目标对象就是被代理对象
    private Object target;

    public PayServiceJDKInvocationHandler(Object target) {
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");
        // 通过反射调用被代理的方法
        Object retVal = method.invoke(target,args);
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target = new AliPayService();
        // 方法调用处理器
        InvocationHandler handler = new PayServiceJDKInvocationHandler(target);
        // 创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
        PayService proxy =(PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler);
        proxy.pay();
    }
}

8. CGLIB 动态代理实现

java 复制代码
public interface PayService {
    void pay();
}
java 复制代码
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    // 被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");
        // 通过反射调用被代理的方法
        Object retVal = methodProxy.invoke(target,args);
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target = new AliPayService();
        PayService proxy =(PayService) Enhancer.create(target.getClass(),
                new PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

9. 总结

AOP 是对某方面能力的统⼀实现,它是⼀种实现思想,Spring AOP 是对 AOP 的具体实现,Spring AOP 可通过 AspectJ(注解)的方式来实现 AOP 的功能,Spring AOP 的实现步骤是:

  1. 添加 AOP 框架支持;
  2. 定义切面和切点;
  3. 定义通知。

Spring AOP 是通过动态代理的方式,在运行期将 AOP 代码织入到程序中的,它的实现方式有两种: JDK Proxy 和 CGLIB。

相关推荐
繁依Fanyi4 分钟前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
慧都小妮子15 分钟前
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
java·pdf·.net
m512720 分钟前
LinuxC语言
java·服务器·前端
IU宝24 分钟前
C/C++内存管理
java·c语言·c++
瓜牛_gn25 分钟前
依赖注入注解
java·后端·spring
hakesashou26 分钟前
Python中常用的函数介绍
java·网络·python
佚先森35 分钟前
2024ARM网络验证 支持一键云注入引流弹窗注册机 一键脱壳APP加固搭建程序源码及教程
java·html
Estar.Lee42 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪44 分钟前
Django:从入门到精通
后端·python·django
一个小坑货44 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust