《设计模式》代理模式

《设计模式》设计模式的基本原则 《设计模式》单例模式 《设计模式》工厂模式 《设计模式》原型模式 《设计模式》建造者模式 《设计模式》适配器模式 《设计模式》桥接模式 《设计模式》装饰者模式 《设计模式》组合模式 《设计模式》外观模式 《设计模式》享元模式 《设计模式》代理模式 《设计模式》模板方法模式 《设计模式》命令模式

@TOC

1. 基本介绍

定义

  • 代理模式在客户端和目标对象之间引入一个代理对象,由代理对象来完成客户端请求的处理,并将请求传递给目标对象,这样就可以提供额外的功能性操作来扩展目标对象的功能,而无需修改目标对象本身。

代理模式的角色组成

  • 抽象主题(Subject):定义了代理和目标对象的公共接口,这样代理对象就可以通过实现该接口来代理目标对象。
  • 真实主题(RealSubject):定义了代理对象所代表的目标对象,是代理对象所要代理的真正对象。
  • 代理对象(Proxy):持有一个真实主题的引用,在其内部实现了与真实主题相同的接口方法,客户端通过代理对象来访问真实主题。并且,它还可以扩展真实主题的功能。

代理模式类图如下所示

2. 静态代理

案例背景

每到节假日来临前后,火车站一定是人流量最大的场所之一。由于现在的网络购票途径非常成熟,因此大家可以在手机上简单操作几下就可以将票买到手了,非常便利。即使是在网络购票的方式出来之前,我记得在我小的时候就经常看见一些门店会贴着火车票的代售点的字样,在那个网络还不发达的年代,这也算是比较方便的购票方式了,总比跑到火车站现场购票方便很多。如果使用代理模式的思想分析这个生活中的场景,那么火车站就可以看作是目标对象 ,而代售点就是代理对象 ,我们通过代售点进行买票,火车站和代售点都有售票的功能,"我们"就是访问对象

设计类图如下所示

静态代理的实现步骤

  • 首先,定义一个接口(抽象主题),并定义一个实现该接口的类(目标对象)。
  • 然后,创建一个代理类来实现接口(抽象主题)。
  • 最后,将目标对象注入到代理类中,并在代理类的重载方法中调用目标对象的方法,可以在目标对象方法调用前后增加一些操作,作为扩展功能。

BookTicketsService 接口:

java 复制代码
public interface BookTicketsService {
    void book();
}

BookTicketsServiceImpl 类:

java 复制代码
/**
 * 目标对象
 */
public class BookTicketsServiceImpl implements BookTicketsService{
    @Override
    public void book() {
        System.out.println("订票");
    }
}

BookTicketsProxy 类:

java 复制代码
public class BookTicketsProxy implements BookTicketsService{

    private final BookTicketsService bookTicketsService;

    public BookTicketsProxy(BookTicketsService bookTicketsService) {
        this.bookTicketsService = bookTicketsService;
    }

    @Override
    public void book() {
        System.out.println("订票前do something...");
        bookTicketsService.book();
        System.out.println("订票后do something...");
    }
}

Client 类:

java 复制代码
/**
 * 客户端类
 */
public class Client {
    public static void main(String[] args) {
        BookTicketsService target = new BookTicketsServiceImpl();
        BookTicketsProxy bookTicketsProxy = new BookTicketsProxy(target);
        bookTicketsProxy.book();
    }
}

访问对象 Client 通过代理对象 BookTicketsProxy 进行订票,代理对象可以在订票前后扩展一额外的功能。

静态代理的总结

  • 在不修改目标对象功能的前提下,实现对目标功能扩展。
  • 代理对象需要与目标对象实现相同的接口,会有很多代理类,当接口方法增加时,需要对目标对象和代理对象都进行维护。

3. JDK 动态代理

JDK 动态代理是 Java 自带的动态代理技术,它可以在运行时动态地创建代理对象 ,并且只能代理接口。使用 JDK 动态代理时,需要定义一个接口,并使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来动态地创建代理对象。

JDK 动态代理的实现步骤

  • 首先,定义一个接口(抽象主题),并定义一个实现该接口的类(目标对象)。
  • 然后 ,实现 InvocationHandler 接口并重写 invoke() 方法,在 invoke() 方法中调用本地方法,并可以在此处加一些扩展操作。
  • 最后 ,通过 Proxy.newProxyInstance() 创建代理对象。

BookTicketsService 接口:

java 复制代码
public interface BookTicketsService {
    void book();
}

BookTicketsServiceImpl 类:

java 复制代码
public class BookTicketsServiceImpl implements BookTicketsService {
    @Override
    public void book() {
        System.out.println("订票");
    }
}

自定义 MyInvocationHandler 类:

java 复制代码
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("订票前do something...");
        // 执行目标对象
        Object result = method.invoke(target, args);
        System.out.println("订票后do something...");
        return result;
    }
}

JdkProxyFactory 类:代理工厂,创建代理对象:

java 复制代码
/**
 * JdkProxy工厂,创建代理对象
 */
public class JdkProxyFactory {
    public static Object getObjectProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 获取目标对象的加载器,加载代理类
                target.getClass().getInterfaces(), // 目标对象实现的接口
                new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
        );
    }
}

Client 类:

java 复制代码
/**
 * 客户端类
 */
public class Client {
    public static void main(String[] args) {
        BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
        bookTicketsService.book();
    }
}

为了更好地观察动态代理类在内存中的创建过程,需要使用 Java 诊断工具 arthas 来打印出程序在运行过程中代理类的结构,步骤如下:

  1. 启动 Client 类,使得程序一直保持运行,因为程序运行结束内存便会被释放,则无法观察到动态代理类在程序运行过程中的结构,并打印出代理对象的类名,方便后面 arthas 工具进行查看,Client 类代码如下:
java 复制代码
public class Client {
    public static void main(String[] args) {
        BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
        bookTicketsService.book();

        System.out.println(bookTicketsService .getClass());

        while (true) {
            
        }
    }
}
  1. 下载 arthas-jar 工具,点击下载跳转地址
  2. 打开命令行窗口,进入 arthas-boot.jar 所在根目录
  3. 输入命令:java -jar arthas-boot.jar
  4. 找到启动类名称 Client,并输入其对应的序号,如下所示:
  5. 加载完成之后,输入命令 jad com.sun.proxy.$Proxy0,等待打印出来的程序运行过程中代理类的结构,如下所示:
  6. 完整代码如下:
java 复制代码
package com.sun.proxy;

import com.hzz.proxy.dynamicproxy.jdk.BookTicketsService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
extends Proxy
implements BookTicketsService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.hzz.proxy.dynamicproxy.jdk.BookTicketsService").getMethod("book", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void book() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
  • 可以看到,代理类 $Proxy0 其实是实现了 BookTicketsService 接口的,只不过是通过 Proxy.newProxyInstance() 方法帮我们实现的,不用像在静态代理中那样显式实现,因此代理类和目标类都要实现相同的接口还是成立的。
  • 此外,从源码中可以看到在 Proxy.newProxyInstance() 方法中创建的类中传递了 target 目标对象,被传递给了 $Proxy0 的父类 Proxy.

根据类的结构可知,动态代理的执行流程大概如下

  1. 首先 ,在 Client 中通过代理对象调用 book 方法。
  2. 之后 ,根据多态性,执行的是代理类 $Proxy0 中的 book() 方法。
  3. 然后 ,代理类 $Proxy0 中的 book() 方法又去调用 InvocationHandler 接口的子实现类对象的 invoke() 方法。
  4. 最后invoke() 方法通过反射执行了目标类 BookTicketsServiceImpl 中的 book() 方法。

JDK 动态代理总结

  • 使用 JDK 动态代理时,不能代理 privatestatic 方法,代理类和目标类需要实现相同的接口,因为 privatestatic 不能修饰接口。
  • JDK 动态代理只能对接口进行代理,不能对普通类进行代理,因为 JDK 动态代理类生成的 $Proxy0 类的父类为 Proxy 类,Java 中不支持多继承。

4. CGLIB 动态代理

从上面两节可以知道,无论是静态代理还是 JDK 动态代理,都需要目标类去实现一个接口,但是有时目标对象就只是一个单独的对象,并没有去实现任何的接口,这时如果还想使用代理模式的话,就可以使用 CGLIB 动态代理。

CGLIB(Code Generation Library) 动态代理

  • CGLIB 通过动态生成一个子类,该子类继承被代理类,重写被代理类的所有非 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用。
  • CGLIB 代理为 JDK 动态代理提供了很好的补充,作为一个功能强大且高性能的代码生成包,可以为没有实现接口的类提供代理,被广泛应用于 AOP 框架中,实现方法的拦截作用。

CGLIB 的 jar 坐标如下所示:

xml 复制代码
<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2.2</version>
</dependency>

JDK动态代理的实现步骤

  • 首先,定义一个目标对象。
  • 然后 ,实现 MethodInterceptor接口,并重写intercept方法,与JDK动态代理中的invoke()方法类似,intercept()方法用来拦截增强代理类的方法。
  • 最后 ,通过Enhancer类的create()方法创建代理对象。

BookTicketsService 类:目标对象

java 复制代码
public class BookTicketsService {
    public void book() {
        System.out.println("订票");
    }
}

自定义的 MyMethodInterceptor 类:

java 复制代码
public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * 重写 intercept 方法,在该方法中调用目标对象的方法
     * @param o 代理对象
     * @param method 目标对象的方法的 method 实例
     * @param objects 方法entry
     * @param methodProxy 代理对象中的方法 method 实例
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("订票前do something");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("订票后do something");
        return result;
    }
}

CglibProxyFactory 类:

java 复制代码
/**
 * CglibProxy工厂,创建代理对象
 */
public class CglibProxyFactory {
    public static Object getObjectProxy(Class<?> clazz) {
        // 创建Enhancer对象,类似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        return enhancer.create();
    }
}

Client 类:

java 复制代码
/**
 * 客户端类
 */
public class Client {
    public static void main(String[] args) {
        BookTicketsService bookTicketsService = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsService.class);
        bookTicketsService.book();
    }
}

CGLIB 代理的注意事项

  • CGLIB 包的底层是使用字节码处理框架 ASM 来转换字节码并生成新的类。
  • 在内存中动态构建子类,被代理的类不能为 final,因为被继承的父类如果为常量类那么无法被继承,会报错 java.lang.IllegalArgumentException.
  • 目标对象的方法不能为 private,因为子类无法访问父类的私有方法;目标对象的方法不能为 final,因为子类无法重写父类的不可变方法;目标对象的方法不能为 static,因为静态方法属于类,是不属于对象的。

5. CGLIB和JDK动态代理性能对比实验

为了了解 CGLIB 和 JDK 这两种动态代理方式性能,设计如下实验:分别使用 CGLIB 和 JDK 两种动态代理方式生成代理对象,运行次数从1000w次到1亿次,每次对比运行次数分别递增1000w次。其中实验的Java环境分别为 JDK1.6,JDK1.8 以及JDK11,cglib 版本为 2.2.2.

5.1 实验源码

抽象主题接口 BookTicketsService

java 复制代码
public interface BookTicketsService {
    void book();
}

目标对象 BookTicketsServiceImpl

java 复制代码
public class BookTicketsServiceImpl implements BookTicketsService {
    @Override
    public void book() {
        int arr[] = {8, 5, 3, 2, 4};

        // 冒泡排序
        for (int i = 0; i < arr.length; i++) {
            // 外层循环,遍历次数
            for (int j = 0; j < arr.length - i - 1; j++) {
                // 内层循环,升序(如果前一个值比后一个值大,则交换)
                // 内层循环一次,获取一个最大值
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }

        // 选择排序
        for (int i = 0; i < arr.length; i++) {
            // 默认第一个是最小的。
            int min = arr[i];
            // 记录最小的下标
            int index = i;
            // 通过与后面的数据进行比较得出,最小值和下标
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {
                    min = arr[j];
                    index = j;
                }
            }
            // 然后将最小值与本次循环的,开始值交换
            int temp = arr[i];
            arr[i] = min;
            arr[index] = temp;
        }

        // 插入排序
        for (int i = 1; i < arr.length; i++) {
            // 外层循环,从第二个开始比较
            for (int j = i; j > 0; j--) {
                // 内存循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
                if (arr[j] < arr[j - 1]) {
                    int temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                } else {
                    // 如果不小于,说明插入完毕,退出内层循环
                    break;
                }
            }
        }
    }
}

实现CGLIB动态代理自定义的 MyMethodInterceptor

java 复制代码
public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * @param o 代理对象
     * @param method 被拦截的方法
     * @param objects 方法entry
     * @param methodProxy 调用目标对象方法的代理
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}

CGLIB 动态代理工厂 CglibProxyFactory 类:

java 复制代码
/**
 * CglibProxy工厂,创建代理对象
 */
public class CglibProxyFactory {
    public static Object getObjectProxy(Class<?> clazz) {
        // 创建动态代理增强对象
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

实现JDK动态代理自定义的 MyInvocationHandler

java 复制代码
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 执行目标对象
        Object result = method.invoke(target, args);
        return result;
    }
}

JDK 动态代理工厂 JdkProxyFactory 类

java 复制代码
/**
 * JdkProxy工厂,创建代理对象
 */
public class JdkProxyFactory {
    public static Object getObjectProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 获取目标类的加载器,加载目标类
                target.getClass().getInterfaces(), // 代理需要实现的接口
                new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
        );
    }
}

测试对比 ComparsionTest

java 复制代码
public class ComparisonTest {

    public static void main(String[] args) {
        BookTicketsService bookTicketsService = new BookTicketsServiceImpl();
        BookTicketsService jdkProxy = (BookTicketsService) JdkProxyFactory.getObjectProxy(bookTicketsService);
        BookTicketsService cglibProxy = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsServiceImpl.class);
        long[] runCounts = new long[]{10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, 100000000};
        Map<String, BookTicketsService> proxyMap = new HashMap<>(3);
        proxyMap.put("jdkProxy", jdkProxy);
        proxyMap.put("cglibProxy", cglibProxy);
        runWithoutMonitor(proxyMap);
        runWithMonitor(proxyMap, runCounts);
    }

    // 预热
    private static void runWithoutMonitor(Map<String, BookTicketsService> proxyMap) {
        for (int i = 0; i < 1; i++) {
            for (String key : proxyMap.keySet()) {
                for (int j = 0; j < 100000; j++) {
                    proxyMap.get(key).book();
                }
            }
        }

    }

    private static void runWithMonitor(Map<String, BookTicketsService> proxyMap, long[] runCounts) {
        for (int i = 0; i < runCounts.length; i++) {
            System.out.println("------------------[jdk version = 1.8.0_301] [运行"+runCounts[i]+"次]---------------------");
            for (String key : proxyMap.keySet()) {
                long start = System.currentTimeMillis();
                for (int j = 0; j < runCounts[i]; j++) {
                    proxyMap.get(key).book();
                }
                long end = System.currentTimeMillis();
                System.out.println("["+ key + "] Elapsed Time:" + (end-start) + "ms");
            }
        }

    }
}

5.2 实验结果

5.2.1 jdk 1.6 版本下的实验结果

IDE 中运行结果截图

cglib 和 jdk 动态代理性能对比实验数据

运行次数 cglib 时间消耗(ms) jdk 时间消耗
10000000 359 365
20000000 610 624
30000000 907 929
40000000 1191 1226
50000000 1492 1589
60000000 1807 1853
70000000 2095 2159
80000000 2450 2515
90000000 2756 2841
100000000 3052 3155

cglib和jdk动态代理性能对比柱状图

5.2.2 jdk 1.8 版本下的实验结果

IDE 中运行结果截图

cglib 和 jdk 动态代理性能对比实验数据

运行次数 cglib 时间消耗(ms) jdk 时间消耗
10000000 284 292
20000000 502 431
30000000 744 668
40000000 910 866
50000000 1158 1115
60000000 1379 1292
70000000 1543 1512
80000000 1811 1713
90000000 2028 1951
100000000 2292 2143

cglib 和 jdk 动态代理性能对比柱状图

5.2.3 jdk 11 版本下的实验结果

IDE 中运行结果截图

cglib 和 jdk 动态代理性能对比实验数据

运行次数 cglib 时间消耗(ms) jdk 时间消耗
10000000 360 325
20000000 494 439
30000000 642 631
40000000 836 836
50000000 1054 1060
60000000 1267 1254
70000000 1487 1454
80000000 1685 1650
90000000 1931 1893
100000000 2087 2045

cglib和jdk动态代理性能对比柱状图

5.3 实验结果分析

  • 可以看到,在 JDK 1.6环境下,JDK 动态代理的性能确实是不如 CGLIB 动态代理的性能,JDK 动态代理对象所需要的时间消耗明显要比CLGIB动态代理对象多。
  • 当时到了JDK1.8版本,JDK动态代理对象所需要的时间消耗已经和 CGLIB 动态代理的时间性能相当甚至已经实现超越,最起码可以肯定的是在1.8环境下,JDK动态代理的效率已经改善了很多,这主要得益于JDK中反射技术的优化升级。
  • 同样地,在JDK11环境下,JDK动态代理的性能依旧保持较好,不会再像JDK1.6时的动态代理性能那么差了。JDK8 和 JDK11 是目前市面上使用较多的两个版本,甚至以后会升级到 JDK17 版本,因为从 springboot 3.0 开始只支持 JDK 17以上版本。
  • 本次实验我使用的 CGLIB 版本为 2.2.2,其实在较新的 3.3.0 版本实验中,CGLIB 动态代理表现出来的性能依然比 JDK 动态代理好的,当时不会好 10 倍以上。
  • 目前,JDK动态代理和CGLIB动态代理的性能并不会相差很多,使用哪种动态代理方式往往不是因为性能的差别,而是因为这两种代理方式本身的特点决定的,JDK动态代理只支持接口,而CGLIB动态代理既支持接口又支持类,往往出于场景的考虑决定使用哪种代理方式,性能差距我觉得可以忽略不记

6. 区别比较

静态代理和动态代理的区别

  • 如果接口增加一个方法,静态代理除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
  • 动态代理最大的优点就是接口中声明的所有方法都被转移到调用处理器的方法 InvocationHandler.invoke 中处理。 在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

JDK 代理和 CGLIB 代理的区别

  • 如果有接口则使用 JDK 动态代理,如果没有接口则使用 CGLIB 动态代理,因为 CGLIB 不仅可以代理接口还能代理普通类。
  • JDK 动态代理使用 Java 反射技术进行操作,在生成类上更高效。CGLIB 使用 ASM 框架直接对字节码进行修改,使用了 FastClass 的特性。在某些情况下,类的方法执行会比较高效

7. 代理模式在框架中的应用

我们知道,Spring 中有一个重要的概念就是面向切面编程(AOP),它很好地与 OOP 相配合,可以帮助我们做一下日志处理、权限控制以及事务管理的操作。因此,Spring 中对 AOP 的实现机制也有相关说明:

大概意思就是:

  • Spring 官方默认使用标准的 JDK 动态代理作为 AOP 的代理方式,可以代理任何接口。
  • 如果一个业务对象没有实现任何接口,那么 AOP 就会使用 CGLIB 动态代理。

但是,在 SpringBoot 中我们发现,对于 SpringBoot 2.x 以前的版本,默认使用的是 JDK 动态代理实现 AOP,具体可以见类 AopAutoConfiguration

SpringBoot 1.5 版本中 AOP 的代理机制:

java 复制代码
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = true)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = false)
	public static class CglibAutoProxyConfiguration {

	}
}

在 SpringBoot 1.5 版本中:

  • SpringBoot 默认使用 JDK 动态代理作为 AOP 代理的方式。

SpringBoot 2.7 版本中 AOP 的代理机制:

java 复制代码
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = false
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false",
            matchIfMissing = false
        )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }

	@Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }
}

在 SpringBoot 2.7 版本中:

  • SpringBoot 默认使用 CGLIB 动态代理作为 AOP 代理的方式。

至于为什么要更改 springboot 中 AOP 默认代理方式,首先看一下之前 Spring 的开发者们在 github 上的讨论

正如上图所示:Phil Webb 提到使用 @EnableTransactionManagement(proxyTargetClass = true),也就是说建议使用 CGLIB 动态代理方式,以防有人不使用接口时,可以避免带来一些令人讨厌的问题。这里的令人讨厌的问题就是 JDK 动态代理不能代理类,只能代理接口这一局限性导致的。因此,从 springboot 2.x 开始,AOP 代理的方式默认都为 CGLIB 动态代理而不是 JDK 动态代理。

当然,如果说你非要使用 JDK 动态代理的话,也可以通过配置文件的方式指定:spring.aop.proxy-target-class = false

相关推荐
姜学迁13 分钟前
Rust-枚举
开发语言·后端·rust
【D'accumulation】1 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_854391081 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
Cikiss1 小时前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
Cikiss1 小时前
微服务实战——平台属性
java·数据库·后端·微服务
OEC小胖胖1 小时前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617622 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
计算机学姐2 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
Yvemil73 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
2401_854391083 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端