前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >似懂非懂的 AspectJ

似懂非懂的 AspectJ

作者头像
江南一点雨
发布于 2023-09-09 02:58:33
发布于 2023-09-09 02:58:33
38300
代码可运行
举报
文章被收录于专栏:玩转JavaEE玩转JavaEE
运行总次数:0
代码可运行
今天想和小伙伴们聊一下我们在使用 Spring AOP 时,一个非常常见的概念 AspectJ。

1. 关于代理

小伙伴们知道,Java 23 种设计模式中有一种模式叫做代理模式,这种代理我们可以将之称为静态代理,Spring AOP 我们常说是一种动态代理,那么这两种代理的区别在哪里呢?

1.1 静态代理

这种代理在我们日常生活中其实非常常见,例如房屋中介就相当于是一个代理,当房东需要出租房子的时候,需要发布广告、寻找客户、清理房间。。。由于比较麻烦,因此房东可以将租房子这件事情委托给中间代理去做。这就是一个静态代理。

我通过一个简单的代码来演示一下,首先我们有一个租房的接口,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Rent {
    void rent();
}

房东实现了该接口,表示想要出租房屋:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Landlord implements Rent{
    @Override
    public void rent() {
        System.out.println("房屋出租");
    }
}

中介作为中间代理,也实现了该接口,同时代理了房东,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HouseAgent implements Rent {
    private Landlord landlord;

    public HouseAgent(Landlord landlord) {
        this.landlord = landlord;
    }

    public HouseAgent() {
    }

    @Override
    public void rent() {
        publishAd();
        landlord.rent();
        agencyFee();
    }

    public void publishAd() {
        System.out.println("发布招租广告");
    }

    public void agencyFee() {
        System.out.println("收取中介费");
    }
}

可以看到,中介的 rent 方法中,除了调用房东的 rent 方法之外,还调用了 publishAd 和 agencyFee 两个方法。

接下来客户租房,只需要和代理打交道就可以了,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Client {
    public static void main(String[] args) {
        Landlord landlord = new Landlord();
        HouseAgent houseAgent = new HouseAgent(landlord);
        houseAgent.rent();
    }
}

这就是一个简单的代理模式。无论大家是否有接触过 Java 23 种设计模式,上面这段代码应该都很好理解。

这是静态代理。

1.2 动态代理

动态代理讲究在不改变原类原方法的情况下,增强目标方法的功能,例如,大家平时使用的 Spring 事务功能,在不改变目标方法的情况下,就可以通过动态代理为方法添加事务处理能力。再比如松哥在 TienChin 项目中所讲的日志处理、接口幂等性处理、多数据源处理等,都是动态代理能力的体现:

从实现原理上,我们又可以将动态代理划分为两大类:

  • 编译时增强。
  • 运行时增强。
1.2.1 编译时增强

编译时增强,这种有点类似于 Lombok 的感觉,就是在编译阶段就直接生成了代理类,将来运行的时候,就直接运行这个编译生成的代理类,AspectJ 就是这样一种编译时增强的工具。

AspectJ 全称是 Eclipse AspectJ, 其官网地址是:http://www.eclipse.org/aspectj,截止到本文写作时,目前最新版本为:1.9.7。

从官网我们可以看到 AspectJ 的定位:

  1. 基于 Java 语言的面向切面编程语言。
  2. 兼容 Java。
  3. 易学易用。

使用 AspectJ 时需要使用专门的编译器 ajc。

1.2.2 运行时增强

运行时增强则是指借助于 JDK 动态代理或者 CGLIB 动态代理等,在内存中临时生成 AOP 动态代理类,我们在 Spring AOP 中常说的动态代理,一般是指这种运行时增强。

我们平日开发写的 Spring AOP,基本上都是属于这一类。

2. AspectJ 和 Spring AOP

经过前面的介绍,相信大家已经明白了 AspectJ 其实也是 AOP 的一种实现,只不过它是编译时增强。

接下来,松哥再通过三个具体的案例,来和小伙伴们演示编译时增强和运行时增强。

2.1 AspectJ

首先,在 IDEA 中想要运行 AspectJ,需要先安装 AspectJ 插件,就是下面这个:

安装好之后,我们需要在 IDEA 中配置一下,使用 ajc 编译器代替 javac(这个是针对当前项目的设置,所以可以放心修改):

有如下几个需要修改的点:

  1. 首先修改编译器为 ajc。
  2. 将使用的 Java 版本改为 8,这个一共有两个地方需要修改。
  3. 设置 aspectjtools.jar 的位置,这个 jar 包需要自己提前准备好,可以从 Maven 官网下载,然后在这里配置 jar 的路径,配置完成之后,点击 test 按钮进行测试,测试成功就会弹出来图中的弹框。

对于第 3 步所需要的 jar,也可以在项目的 Maven 中添加如下依赖,自动下载,下载到本地仓库之后,再删除掉 pom.xml 中的配置即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.9.7.M3</version>
</dependency>

这样,开发环境就准备好了。

接下来,假设我有一个银行转帐的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MoneyService {

    public void transferMoney() {
        System.out.println("转账操作");
    }
}

我想给这个方法添加事务,那么我就新建一个 Aspect,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public aspect TxAspect {
    void around():call(void MoneyService.transferMoney()){
        System.out.println("开启事务");
        try {
            proceed();
            System.out.println("提交事务事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
        }
    }
}

这就是 AspectJ 的语法,跟 Java 有点像,但是不太一样。需要注意的是,这个 TxAspect 不是一个 Java 类,它的后缀是 .aj

proceed 表示继续执行目标方法,前后逻辑比较简单,我就不多说了。

最后,我们去运行转账服务:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Demo01 {
    public static void main(String[] args) {
        MoneyService moneyService = new MoneyService();
        moneyService.transferMoney();
    }
}

运行结果如下:

这就是一个静态代理。

为什么这么说呢?我们通过 IDEA 来查看一下 TxAspect 编译之后的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Aspect
public class TxAspect {
    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public TxAspect() {
    }

    @Around(
        value = "call(void MoneyService.transferMoney())",
        argNames = "ajc$aroundClosure"
    )
    public void ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afea(AroundClosure ajc$aroundClosure) {
        System.out.println("开启事务");

        try {
            ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afeaproceed(ajc$aroundClosure);
            System.out.println("提交事务事务");
        } catch (Exception var2) {
            System.out.println("回滚事务");
        }

    }

    public static TxAspect aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("org_javaboy_demo_p2_TxAspect", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

再看一下编译之后的启动类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Demo01 {
    public Demo01() {
    }

    public static void main(String[] args) {
        MoneyService moneyService = new MoneyService();
        transferMoney_aroundBody1$advice(moneyService, TxAspect.aspectOf(), (AroundClosure)null);
    }
}

可以看到,都是修改后的内容了。

所以说 AspectJ 的作用就有点类似于 Lombok,直接在编译时期将我们的代码改了,这就是编译时增强。

2.2 Spring AOP

Spring AOP 在开发的时候,其实也使用了 AspectJ 中的注解,像我们平时使用的 @Aspect、@Around、@Pointcut 等,都是 AspectJ 里边提供的,但是 Spring AOP 并未借鉴 AspectJ 的编译时增强,Spring AOP 没有使用 AspectJ 的编译器和织入器,Spring AOP 还是使用了运行时增强。

运行时增强可以利用 JDK 动态代理或者 CGLIB 动态代理来实现。我分别来演示。

2.2.1 JDK 动态代理

JDK 动态代理有一个要求,就是被代理的对象需要有接口,没有接口不行,CGLIB 动态代理则无此要求。

假设我现在有一个计算器接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ICalculator {
    int add(int a, int b);
}

这个接口有一个实现类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CalculatorImpl implements ICalculator {
    @Override
    public int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
}

现在,我想通过动态代理实现统计该接口的执行时间功能,JDK 动态代理如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Demo02 {
    public static void main(String[] args) {

        CalculatorImpl calculator = new CalculatorImpl();
        ICalculator proxyInstance = (ICalculator) Proxy.newProxyInstance(Demo02.class.getClassLoader(), new Class[]{ICalculator.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                Object invoke = method.invoke(calculator, args);
                long endTime = System.currentTimeMillis();
                System.out.println(method.getName() + " 方法执行耗时 " + (endTime - startTime) + " 毫秒");
                return invoke;
            }
        });
        proxyInstance.add(3, 4);
    }
}

不需要任何额外依赖,都是 JDK 自带的能力:

  1. Proxy.newProxyInstance 方法表示要生成一个动态代理对象。
  2. newProxyInstance 方法有三个参数,第一个是一个类加载器,第二个参数是一个被代理的对象所实现的接口,第三个则是具体的代理逻辑。
  3. 在 InvocationHandler 中,有一个 invoke 方法,该方法有三个参数,分别表示当前代理对象,被拦截下来的方法以及方法的参数,我们在该方法中可以统计被拦截方法的执行时间,通过方式执行被拦截下来的目标方法。
  4. 最终,第一步的方法返回了一个代理对象,执行该代理对象,就有代理的效果了。

上面这个案例就是一个 JDK 动态代理。这是一种运行时增强,在编译阶段并未修改我们的代码。

2.2.2 CGLIB 动态代理

从 SpringBoot2 开始,AOP 默认使用的动态代理就是 CGLIB 动态代理了,相比于 JDK 动态代理,CGLIB 动态代理支持代理一个类。

使用 CGLIB 动态代理,需要首先添加依赖,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

假设我有一个计算器,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Calculator {
    public int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
}

大家注意,这个计算器就是一个实现类,没有接口。

现在,我想统计这个计算器方法的执行时间,首先,我添加一个方法执行的拦截器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CalculatorInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = methodProxy.invokeSuper(o, objects);
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + " 方法执行耗时 " + (endTime - startTime) + " 毫秒");
        return result;
    }
}

当把代理方法拦截下来之后,额外要做的事情就在 intercept 方法中完成。通过执行 methodProxy.invokeSuper 可以调用到代理方法。

最后,配置 CGLIB,为方法配置增强:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Demo03 {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Calculator.class);
        enhancer.setCallback(new CalculatorInterceptor());
        Calculator calculator = (Calculator) enhancer.create();
        calculator.add(4, 5);
    }
}

这里其实就是创建了字节增强器,为生成的代理对象配置 superClass,然后设置拦截下来之后的回调函数就行了,最后通过 create 方法获取到一个代理对象。

这就是 CGLIB 动态代理。

3. 小结

经过上面的介绍,现在大家应该搞明白了静态代理、编译时增强的动态代理和运行时增强的动态代理了吧~

那么我们在项目中到底该如何选择呢?

先来说 AspectJ 的几个优势吧。

  1. Spring AOP 由于要生成动态代理类,因此,对于一些 static 或者 final 修饰的方法,是无法代理的,因为这些方法是无法被重写的,final 修饰的类也无法被继承。但是,AspectJ 由于不需要动态生成代理类,一切都是编译时完成的,因此,这个问题在 AspectJ 中天然的就被解决了。
  2. Spring AOP 有一个局限性,就是只能用到被 Spring 容器管理的 Bean 上,其他的类则无法使用,AspectJ 则无此限制(话说回来,Java 项目 Spring 基本上都是标配了,所以这点其实到也不重要)。
  3. Spring AOP 只能在运行时增强,而 AspectJ 则支持编译时增强,编译后增强以及运行时增强。
  4. Spring AOP 支持方法的增强,然而 AspectJ 支持方法、属性、构造器、静态对象、final 类/方法等的增强。
  5. AspectJ 由于是编译时增强,因此运行效率也要高于 Spring AOP。
  6. 。。。

虽然 AspectJ 有这么多优势,但是 Spring AOP 却有另外一个制胜法宝,那就是简单易用

所以,我们日常开发中,还是 Spring AOP 使用更多。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江南一点雨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
人脸姿态校正算法 附完整C++示例代码
那么假如一张图片只有一个人脸,其实很好判断,通过眼睛的位置的坐标,根据两眼的直线角度,
cpuimage
2018/05/08
2.7K14
人脸姿态校正算法 附完整C++示例代码
13行代码实现最快速最高效的积分图像算法。
本文介绍了基于积分图像的计算机图像处理技术,包括其基本概念、操作步骤和应用场景。通过积分图像,可以方便地进行图像的局部特征提取和图像处理,包括图像滤波、边缘检测、图像分割和特征提取等。同时,本文还介绍了一些常用的积分图像处理算法,包括卷积、高斯滤波、Canny边缘检测和霍夫变换等。这些算法在计算机视觉和图像处理领域都有广泛的应用,对于图像处理的效果和性能有着重要的影响。
用户1138785
2018/01/03
1.8K0
13行代码实现最快速最高效的积分图像算法。
自动曝光修复算法 附完整C代码
AF自动对焦(Automatic Focus) 自动对焦即调节摄像头焦距自动得到清晰的图像的过程
cpuimage
2018/06/02
2.9K0
半径无关快速高斯模糊实现(附完整C代码)
之前,俺也发过不少快速高斯模糊算法. 俺一般认为,只要处理一千六百万像素彩色图片,在2.2GHz的CPU上单核单线程超过1秒的算法,都是不快的. 之前发的几个算法,在俺2.2GHz的CPU上耗时都会超过1秒. 而众所周知,快速高斯模糊有很多实现方法: 1.FIR (Finite impulse response) https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A 2.SII (Stacked integral images)
cpuimage
2018/04/12
2.3K1
半径无关快速高斯模糊实现(附完整C代码)
【AI PC端算法优化】三,深入优化RGB转灰度图算法
前几天发了一篇一步步优化RGB转灰度图算法,但实验做的并不完善,在上次的基础上我又补充了一些优化技巧,相对于传统实现将RGB转灰度图算法可以加速到近5倍左右。所以,这篇文章再次将所有涉及到的优化方法进行汇总,SSE优化相关的原理上一节已经讲得很清楚了,这里就不会再展开了,感兴趣可以查看上篇文章。【AI PC端算法优化】一,一步步优化RGB转灰度图算法 这一节的速度测试环境为:
BBuf
2020/04/15
1.2K0
3D Lut 电影级调色算法 附完整C代码
长话短说,3d lut(全称 : 3D Lookup table )它是通过建立一个颜色映射表,对图像的色调进行重调的算法。
cpuimage
2018/05/11
4.5K2
【AI PC端算法优化】七,一步步优化RGB和YUV互转算法
继续学习指令集优化的知识,今天来讨论一个图像颜色空间转换经常碰到的一个问题即RGB和YUV图像的颜色空间转换,我们从原理介绍和普通实现开始,然后介绍一些优化方法并引入SSE指令集来优化这个算法的速度。
BBuf
2020/05/21
1.9K0
【AI PC端算法优化】七,一步步优化RGB和YUV互转算法
MTCNN人脸检测 附完整C++代码
Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks
cpuimage
2018/05/07
4.6K8
MTCNN人脸检测 附完整C++代码
快速双边滤波 附完整C代码
很早之前写过《双边滤波算法的简易实现bilateralFilter》。 当时学习参考的代码来自cuda的样例。 相关代码可以参阅: https://github.com/johng12/cudaSamples/tree/master/cudaSamples/3_Imaging/bilateralFilter 由于算法逻辑非常清晰,就不多解释了。 需要补课的,请移步《o(1)复杂度之双边滤波算法的原理、流程、实现及效果。》 代码见:bilateralFilter_cpu.cpp 文件。 #include <m
cpuimage
2018/04/12
4.8K0
快速双边滤波 附完整C代码
C语言100例(51-60)
51,题目:学习使用按位与 & 。 程序分析:0&0=0; 0&1=0; 1&0=0; 1&1=1 程序源代码:
紫禁玄科
2022/03/24
3490
SSE图像算法优化系列二:高斯模糊算法的全面优化过程分享(一)。
根据文章内容总结的摘要
用户1138785
2018/01/03
2.2K0
SSE图像算法优化系列二:高斯模糊算法的全面优化过程分享(一)。
【AI PC端算法优化】四,一步步将Sobel边缘检测加速22倍
继续优化技术的探索,今天以一个的Sobel算子进行边缘检测的算法为例来看看如何使用SSE指令集对其进行优化。
BBuf
2020/04/20
1.5K0
【AI PC端算法优化】四,一步步将Sobel边缘检测加速22倍
SSE图像算法优化系列一:一段BGR2Y的SIMD代码解析。
该文章是一篇关于Linux、Windows和macOS操作系统之间区别的文章。文章主要介绍了Linux、Windows和macOS这三种操作系统在桌面环境、图形界面、文件系统、系统管理、软件安装、系统性能、安全性、适用范围等方面的区别。文章还探讨了每种操作系统的优缺点,以及适用场景。最后,作者提供了一些建议,帮助读者选择适合自己的操作系统。
用户1138785
2018/01/03
1.3K0
自动红眼移除算法 附c++完整代码
“红眼”一般是指在人物摄影时,当闪光灯照射到人眼的时候,瞳孔放大而产生的视网膜泛红现象。
cpuimage
2018/05/07
1.6K6
自动红眼移除算法 附c++完整代码
AVX图像算法优化系列二: 使用AVX2指令集加速查表算法。
  查表算法,无疑也是一种非常常用、有效而且快捷的算法,我们在很多算法的加速过程中都能看到他的影子,在图像处理中,尤其常用,比如我们常见的各种基于直方图的增强,可以说,在photoshop中的调整菜单里80%的算法都是用的查表,因为他最终就是用的曲线调整。
用户1138785
2022/10/28
1.6K0
AVX图像算法优化系列二: 使用AVX2指令集加速查表算法。
C语言经典编程题100例 51~60
51、学习使用按位与 &。 程序分析: 0&0=0; 0&1=0; 1&0=0; 1&1=1 。 参考代码: #include <stdio.h> int main() { int a,b; a=077; b=a&3; printf("a & b(decimal) 为 %d \n",b); b&=7; printf("a & b(decimal) 为 %d \n",b); return 0; } 运行结果: a & b(decimal) 为 3 a
C you again
2022/08/22
1.2K0
SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。
本文介绍了如何利用SSE优化灰度图算法,通过在RGB空间上进行自适应直方图均衡和基于Alpha的抖动算法,实现了灰度图算法的高性能优化。
用户1138785
2018/01/03
1.3K0
SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。
简易 bokeh 图像散景效果算法实现
bokeh百度百科的解释 摄影镜头光圈大小和拍摄距离决定了拍摄时的景深,相对于焦点位置,焦点前与焦点后的被拍摄物体会显得模糊,这个模糊区域被称为焦外。 焦外具体的模糊程度还受到镜头中镜片单体和组合的物理特性影响,形成了由镜头不同而得到的不同的焦点外的图像。于是焦外成像这个技术名词出现了。 优秀的焦外成像柔顺而迷人,色彩过渡自然,丝毫不逊色于焦点处的图像魅力。 最典型的例子,就是夜景拍摄中的远景模糊炫丽的灯光效果。 由于算法逻辑比较简单,就不多解释。 简单的说就是以半径圆圈内的各通道基于明度进行权重计算。
cpuimage
2018/04/12
1.9K0
【快速阅读二】从OpenCv的代码中扣取泊松融合算子(Poisson Image Editing)并稍作优化
  泊松融合我自己写的第一版程序大概是2016年在某个小房间里折腾出来的,当时是用的迭代的方式,记得似乎效果不怎么样,没有达到论文的效果。前段时间又有网友问我有没有这方面的程序,我说Opencv已经有了,可以直接使用,他说opencv的框架太大,不想为了一个功能的需求而背上这么一座大山,看能否做个脱离那个环境的算法出来,当时,觉得工作量挺大,就没有去折腾,最近年底了,项目渐渐少了一点,公司上面又在搞办公室政治,我地位不高,没有参与权,所以乐的闲,就抽空把这个算法从opencv里给剥离开来,做到了完全不依赖其他库实现泊松融合乐,前前后后也折腾进半个月,这里还是做个开发记录和分享。
用户1138785
2024/01/17
5320
【快速阅读二】从OpenCv的代码中扣取泊松融合算子(Poisson Image Editing)并稍作优化
SSE图像算法优化系列2-高斯滤波
或许大多数人对于高斯滤波的印象都停留在使用一个高斯在图像滑动然后计算结果。这的确没错,但从速度上考虑这种模式是难以优化的。也导致在极大分辨率图像上进行高斯滤波是压根不可行的。幸运的是,高斯滤波实际上还有另外一种表达方式,那就是递归表达。这最早见于《Recursive implementation of the Gaussian filter》论文中:
BBuf
2019/12/04
1.2K0
SSE图像算法优化系列2-高斯滤波
推荐阅读
相关推荐
人脸姿态校正算法 附完整C++示例代码
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 1. 关于代理
    • 1.1 静态代理
    • 1.2 动态代理
      • 1.2.1 编译时增强
      • 1.2.2 运行时增强
  • 2. AspectJ 和 Spring AOP
    • 2.1 AspectJ
    • 2.2 Spring AOP
      • 2.2.1 JDK 动态代理
      • 2.2.2 CGLIB 动态代理
  • 3. 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档