# Spring AOP 的两种实现方案
Spring AOP 可以利用两种不同的方案实现对目标对象的方法的增强。
JDK 动态代理
Cglib 动态代理
# JDK 动态代理
JDK 动态代理是 Spring AOP 的默认实现方案。其基本原理如下图:
接口和被代理类
public interface Animal { void eat(); } public class Cat implements Animal { public void eat() { System.out.println("猫吃鱼"); } }
代理类
public class CatProxy implements Animal { private Cat target; public CatProxy() { this.target = new Cat(); } @Override public void eat() { System.out.println("执行前调用"); target.eat(); System.out.println("执行后调用"); } }
从上述的 UML 图可以看到,JDK 动态代理有一个限制:它要求被代理对象必须有一个接口 。
在整个方案中,如果 Animal
接口并不存在,那么就无法使用 JDK 动态代理方案实现 AOP 功能。
# cglib 动态代理
cglib 是 Spring AOP 的另一个方案,它利用了 cglib 包来实现动态代理功能。
使用 cglib 需要引入 cglib 包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.11</version>
</dependency>
不过由于 Spring AOP 的 org.springframework.cglib
包中包含了 CGLIB 的相关代码,所以也可以选择导入 Spring AOP 的 Maven 依赖 。
Cglib 实现的动态代理基本原理如下:
public class Cat {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class CatProxy extends Cat {
public CatProxy() {
}
@Override
public void eat() {
System.out.println("执行前调用");
super.eat();
System.out.println("执行后调用");
}
}
cglib 方案没有 jdk 方案的限制,它利用的是继承,因此对 Cat 类有无接口不作要求。不过继承也带来一个代价,那就是如果 Cat 类中有 final 方法,那么 CatProxy 无法对 final 方法进行增强。
# 总结与对比
JDK 方案和 Cglib 方案各有优缺点。jdk 方案是 Spring AOP 的默认方案,但是在 Spring Boot 中官方建议大家采用 Cglib 方案。
两种方案各自的缺陷:
- JDK 方案要求被代理对象(Cat)必须是某个接口的实现类。
- Cglib 方案无法增强被代理对象(Cat)的 final 方法。
另外,如果需要代理的对象的体系较复杂(实现了多个接口和较深的继承层次结构),Spring 使用 JDK 实现动态代理时有可能出现问题。这也是 Spring Boot 中建议使用 Cglib 方案的原因。
# 强制使用 Cglib 方案
如果是以 AspectJ 注解的方式使用 Spring AOP,那么配置方式是将元素 <aop:aspectj-autoproxy > 的 proxy-target-class 属性设置为 true 。
<aop:aspectj-autoproxy proxy-target-class="true"/>
如果是通过 Java 代码进行配置的话,则使用 @EnableAspectJAutoProxy(proxyTargetClass=true) 注解。
如果是以 .xml
配置的方式使用 Spring AOP,那么将元素 <aop:config>
的 proxy-target-class
属性的值设置为 true 。
<aop:config proxy-target-class="true">
... <!-- 切面配置 -->
</aop:config>