《Spring实战 第四版》AOP
本文最后更新于 2025年9月14日 下午
Spring中广泛使用了AOP技术让代码看起来简洁,美观且优雅。如果说DI是为了让相互协作的组件保持低耦合性质,那AOP技术便可以看做是为了让组件本身保持高内聚性质。接下来将具体讲讲这个概念。
AOP (Aspect-Oriented Programming) 面向切面编程
我们在实际开发软件的过程当中,通常需要为系统设计许多不同的组件(模块),每个组件各司其职,并通过这些组件的协作来最终构成一个完整流畅的系统。
然而,这些组件为了与其他组件完成协作,通常需要在自己的代码中完成一些其他组件所需要完成的流程。
书中给了一个很好的例子:日志,事务管理,安全这类系统服务经常在其他组件中进行调用,审查等一系列操作。这让其他组件很难完全关注在自己的任务上。
这里给个例子:
1 | |
这个例子中,doStrategy()需要在执行run()之前和之后都打印一条日志。假如这条日志中包含了有关这个Agent信息和相关策略的信息,则必须要在每一个Agent中都定义对应的日志信息。然而,日志的执行实际上不应该与Agent有关,只是由于程序和系统的需要,导致Agent无法单纯地专注于自己的任务。当这类日志的生成来源于一个其他对象,则这个地方便对其他对象有了依赖,同时也会带来额外的不必要的耦合。
那么,我们如何解决这个问题呢?
不难发现,这里的日志生成只是一个固定的流程,这类固定的流程所存在的位置也是固定的,因此我们完全可以用一个方法将这个方法进行修饰,就像是赋予了一个额外的功能一样,只是没有在这个方法体中表现出来。这样一来,Agent便不再需要在意日志的生成,只需要关心自己的任务即可。这种方式,便可以被称为是AOP。
其实也可以想到,这类对一个方法进行功能增强和修饰的方法,不正是代理模式吗?然而,AOP的实现并非如此单纯的事情,它通常需要考虑如何对方法进行拦截,拦截前后所要执行的内容为何等等,总之是一个十分麻烦的流程。但是AOP的存在,给代码的简洁明了和内聚性带来了有一个春天。
AOP的实现方式
对于这个解释,网上通常会给出一个非常笼统且不讲述原因的说法:
当要代理的对象实现了某个接口时,Spring AOP会使用JDK Proxy来创建代理对象。当代理的对象没有实现接口,则使用CGLIB生成一个被代理对象的子类作为代理。
给我听得一愣一愣的,没有弄清楚原因,因此我又继续查了一下。
先讲讲JDK Proxy和CGLIB的区别:
JDK Proxy (Java Dynamic Proxy)
- 这是一个Java的标准库组件,与Java有着良好的适配性(毕竟是内置的)
实现原理如下:
1 | |
这段代码来自于DeepSeek,我也不清楚是不是真是这样。但是我们可以从中看到一个事实,就是它必须要传入一个接口class。
CGLIB (Code Generation Library)
- 这是一个第三方开源库,虽然经过优化后也有较高的性能,但终究无法与Java内置库比拟。
实现原理如下:
1 | |
可以看出来,CGLIB支持完整的类代理,并不刚需接口类。
讨论
Spring在早期版本建议采用JDK Proxy的方式来做这事,开发者需要显式配置spring.aop.proxy-target-class=true来启用CGLIB动态代理或者注入接口。在Spring2.0之后,便默认使用CGLIB进行动态代理,同样将配置设为false即可强制使用JDK Proxy代理。
不过,如今恐怕大多数人会直接使用AspectJ做AOP框架。Spring AOP已经继承了AspectJ,相较于传统的Spring AOP,AspectJ的功能更强大,运行性能相对更强。除了相对难用一些以外,没什么理由用Spring AOP。