《Spring实战 第四版》AOP

本文最后更新于 2025年9月14日 下午

Spring中广泛使用了AOP技术让代码看起来简洁,美观且优雅。如果说DI是为了让相互协作的组件保持低耦合性质,那AOP技术便可以看做是为了让组件本身保持高内聚性质。接下来将具体讲讲这个概念。

AOP (Aspect-Oriented Programming) 面向切面编程

我们在实际开发软件的过程当中,通常需要为系统设计许多不同的组件(模块),每个组件各司其职,并通过这些组件的协作来最终构成一个完整流畅的系统。

然而,这些组件为了与其他组件完成协作,通常需要在自己的代码中完成一些其他组件所需要完成的流程。

书中给了一个很好的例子:日志,事务管理,安全这类系统服务经常在其他组件中进行调用,审查等一系列操作。这让其他组件很难完全关注在自己的任务上。

这里给个例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class GeneralAgent extend Agent {
private Strategy strategy;
public GeneralAgent(Strategy strategy){
this.strategy = strategy;
}

public void doStrategy(){
System.out.println("This agent is going to do the strategy");
this.strategy.run();
System.out.println("This agent finished the strategy");
}
}

这个例子中,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 基于接口的代理
public interface UserService {
void addUser(String name);
}

// JDK Proxy 创建过程
InvocationHandler handler = new InvocationHandler() {
private UserService target = new UserServiceImpl();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
};

UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class}, // 必须传入接口
handler
);

这段代码来自于DeepSeek,我也不清楚是不是真是这样。但是我们可以从中看到一个事实,就是它必须要传入一个接口class。

CGLIB (Code Generation Library)

  • 这是一个第三方开源库,虽然经过优化后也有较高的性能,但终究无法与Java内置库比拟。

实现原理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基于继承的代理
public class UserService {
public void addUser(String name) {
System.out.println("Add user: " + name);
}
}

// CGLIB 创建过程
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 设置父类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("After method: " + method.getName());
return result;
}
});

UserService proxy = (UserService) enhancer.create();

可以看出来,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。


《Spring实战 第四版》AOP
http://example.com/2025/09/12/Spring学习/《Spring实战 第四版》AOP基本概念/
作者
Clain Chen
发布于
2025年9月12日
许可协议