Spring AOP的实现原理

原文出处: Listen

AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP 实现的关键在于AOP框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理,静态代理的代表为 AspectJ;而动态代理则以 Spring AOP 为代表。本文会分别对 AspectJSpring AOP 的实现进行分析和介绍。

一、使用 AspectJ 的编译时增强实现 AOP

之前提到,AspectJ 是静态代理的增强,所谓的静态代理就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强。

举个实例的例子来说。首先我们有一个普通的Hello类

1
2
3
4
5
6
7
8
9
10
public class Hello {
public void sayHello() {
System.out.println("hello");
}

public static void main(String[] args) {
Hello h = new Hello();
h.sayHello();
}
}

使用 AspectJ 编写一个 Aspect

1
2
3
4
5
6
7
public aspect TxAspect {
void around():call(void Hello.sayHello()){
System.out.println("开始事务 ...");
proceed();
System.out.println("事务结束 ...");
}
}

这里模拟了一个事务的场景,类似于 Spring 的声明式事务。使用 AspectJ 的编译器编译

1
ajc -d . Hello.java TxAspect.aj

编译完成之后再运行这个Hello类,可以看到以下输出

开始事务 ...
hello
事务结束 ...

显然,AOP已经生效了,那么究竟 AspectJ 是如何在没有修改Hello类的情况下为Hello类增加新功能的呢?
查看一下编译后的 Hello.class

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Hello {
public Hello() {
}

public void sayHello() {
System.out.println("hello");
}

public static void main(String[] args) {
Hello h = new Hello();
sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
}
}

可以看到,这个类比原来的 Hello.java 多了一些代码,这就是 AspectJ 的静态代理,它会在编译阶段将 AspectJ 织入 Java 字节码中, 运行的时候就是经过增强之后的 AOP 对象。

1
2
3
4
5
public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
System.out.println("开始事务 ...");
ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
System.out.println("事务结束 ...");
}

从 Aspect 编译后的 class 文件可以更明显的看出执行的逻辑。proceed 方法就是回调执行被代理类中的方法。

二、使用Spring AOP

AspectJ 的静态代理不同,Spring AOP 使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。

如果目标类没有实现接口,那么 Spring AOP 会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

为了验证以上的说法,可以做一个简单的测试。首先测试实现接口的情况。

定义一个接口

1
2
3
public interface Person {
String sayHello(String name);
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese implements Person {

@Timer
@Override
public String sayHello(String name) {
System.out.println("-- sayHello() --");
return name + " hello, AOP";
}

public void eat(String food) {
System.out.println("我正在吃:" + food);
}

}

这里的 @Timer 注解是我自己定义的一个普通注解,用来标记 Pointcut。

定义Aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Component
public class AdviceTest {

@Pointcut("@annotation(com.listenzhangbin.aop.Timer)")
public void pointcut() {
}

@Before("pointcut()")
public void before() {
System.out.println("before");
}
}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

//这里必须使用Person接口做注入
@Autowired
private Person chinese;

@RequestMapping("/test")
public void test() {
chinese.sayHello("listen");
System.out.println(chinese.getClass());
}

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

输出

1
2
3
before
-- sayHello() --
class com.sun.proxy.$Proxy53

可以看到类型是 com.sun.proxy.$Proxy53,也就是前面提到的 Proxy 类,因此这里 Spring AOP 使用了 JDK 的动态代理。

再来看看不实现接口的情况,修改 Chinese 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese {

@Timer
// @Override
public String sayHello(String name) {
System.out.println("-- sayHello() --");
return name + " hello, AOP";
}

public void eat(String food) {
System.out.println("我正在吃:" + food);
}

}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

//直接用Chinese类注入
@Autowired
private Chinese chinese;

@RequestMapping("/test")
public void test() {
chinese.sayHello("listen");
System.out.println(chinese.getClass());
}

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

输出

1
2
3
before
-- sayHello() --
class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168

可以看到类被 CGLIB 增强了,也就是动态代理。这里的 CGLIB 代理就是 Spring AOP 的代理,这个类也就是所谓的 AOP 代理,AOP 代理类在切点动态地织入了增强处理。

小结

AspectJ 在编译时就增强了目标对象,Spring AOP 的动态代理则是在每次运行时动态的增强,生成 AOP 代理对象,区别在于生成 AOP 代理对象的时机不同,相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。