Spring AOP 深度剖析与实践
# 写在文章开头
我们日常用AOP解耦业务与非业务功能的关系,但你是否真正了解过以下几个问题:
- Spring AOP是如何实现方法拦截的?
- JDK动态代理和CGLIB代理有什么区别?性能差异大吗?
- 为什么Spring Boot 2.x默认选择CGLIB代理?
本文将从源码层面深入剖析Spring AOP的实现原理,并通过性能测试代码验证JDK动态代理与CGLIB的真实性能差异,让你真正从设计理念上了解AOP 并能够正确的应用这项技术。
你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go、C 等多语言技术栈,现任某知名黑厂高级开发工程师,专注于高并发系统架构设计与性能优化。
🌟 开源项目贡献
- mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
🔗 https://github.com/shark-ctrl/mini-redis (opens new window)(欢迎 Star & Contribute)
📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。
👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!
# 详解Spring对AOP的设计与实现
# 对AOP的理解
AOP(Aspect-Oriented Programming:面向切面编程),一种将业务与非业务职责分离的设计理念,通过降低彼此间的耦合度,确保各个模块间能够专注于各自的职责,降低后期开发维护和迭代的成本,AOP常见应用场景为:
权限校验日志管理事务处理
由于语言的特性,Spring则是采用动态代理完成逻辑增强,在Spring 4.x及之前的的版本,如果被代理的类有实现接口的话,就会基于JDK Proxy完成代理的创建,反之就是通过Cglib完成代理创建,当然你也可以强制使用Cglib。
注:从Spring 5.x开始(也就是Spring Boot 2.x ),配置项proxy-target-class 默认值为 true,这意味着除非目标类是接口类型,默认情况下都使用 CGLIB 完成动态代理创建。

# 什么是AOP中切点、切面、通知
AOP中有很多核心术语,分别是:
目标(Target): 这就被代理的对象,例如我们希望对UserService每个方法进行增强(在不动它的代码情况下增加一些非业务的动作),那么这个UserService就是目标。代理(Proxy): 就是给你被代理后的对象的厂商,例如我们上面说过希望对UserService每个方法进行增强,那么给用户返回增强后的对象的类就是代理类。连接点(JoinPoint):目标对象,每一个可能可以被增强的方法都可以称为连接点,尽管它最后可能不会被增强。切入点(Pointcut): 定义"哪些连接点需要被增强"的表达式,通过表达式(如execution(* com.xxx.Service.*(..)))筛选匹配的连接点。通知(Advice): 不要被表面的语义误导,通知并不是告知某人的意思,通知的意思是拦截对象后,做的增强操作,也就是拦截后要执行什么代码。切面(Aspect): 切入点(Pointcut)+通知(Advice)。织入(Weaving):把通知的动作融入到对象中,生成代理对象的过程就叫做织入。

# Spring AOP和AspectJ AOP的区别
Spring AOP属于运行时增强,基于代理(Proxying)实现的。而AspectJ AOP属于编译时增强,基于字节码操作(Bytecode Manipulation)实现的。
# AspectJ 通知类型
Before(前置通知): 目标对象方法调用前触发增强。After(后置通知/最终通知): 目标对象方法调用后进行增强,无论方法正常返回还是抛出异常都会执行(类似finally块)。AfterReturning(返回通知):目标对象方法执行结束,返回值时进行增强。AfterThrowing(异常通知):目标对象方法执行报错并抛出时做的增强。Around(环绕通知):这个比较常用了,目标对象方法调用前后我们可以做各种增强操作,甚至不调用对象的方法都能做到。
# 多个切面执行顺序我们如何确定
有两种方式:
第一种: 使用@Order注解来决定切面bean的执行顺序。
// 值越小优先级越高
@Order(1)
@Component
@Aspect
public class LoggingAspect implements Ordered {
2
3
4
5
6
第二种: 继承接口法,实现implements Ordered接口显示声明序号:
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# AOP操作在bean生命周期的那个阶段实现
AOP动态增强在bean初始化前后 即BeanPostProcessor接口实现(也就是业内常说的BPP阶段)完成动态代理创建:

对应源码位于AbstractAutoProxyCreator的postProcessAfterInitialization方法完成,这里我们看到代码中一个earlyProxyReferences的remove操作,实际上这是为了避免互相依赖的bean重复增强问题。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 若返回null,则说明被增强的bean没有提前暴露,调用wrapIfNecessary执行增强
return wrapIfNecessary(bean, beanName, cacheKey);//创建代理
}
}
return bean;
}
2
3
4
5
6
7
8
9
10
对此我们可以分为两个场景讨论,
- 假设我创建a对象,没有任何依赖关系,那么下面这段方法返回的就是null,直接完成增强返回。
- 又假设我们需要创建a、b两个对象,且这两个对象互相依赖,按照Spring的创建流程则如下
1. 创建 A(实例化)
↓
2. 属性填充发现依赖 B
↓
3. 创建 B(实例化)
↓
4. B 属性填充发现依赖 A
↓
5. 调用 getEarlyBeanReference(A)
- 将 A 存入 earlyProxyReferences
- 对 A 进行代理增强
↓
6. B 获得增强后的 A,B 创建完成
↓
7. A 继续初始化,执行 postProcessAfterInitialization
- 发现 earlyProxyReferences 中有自己
- 说明之前已增强,直接返回
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 动态代理的基本概念
是在运行期间,创建目标对象的代理对象,目标对象不变,我们通过对目标方法进行动态拦截,在代理方法前后执行各种增强逻辑操作。

# 动态代理的创建过程
我们以最新版的Spring 5.x为例,对应动态创建创建的代码位于DefaultAopProxyFactory核心方法createAopProxy,核心逻辑为:
- 判断isProxyTargetClass(默认为true,2.x版本默认配置)是否为true,若不为true则通过jdk动态代理完成创建,反之进入步骤2
- 判断是否是接口,如果是接口也通过JDK动态创建完成创建(典型实现为mybatis mapper),反之进入步骤3
- 所有默认情况都执行ObjenesisCglibAopProxy通过CGLIB完成动态增强
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// Spring 5.x isProxyTargetClass返回true,默认情况下都会强制走cglib
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
//如果是接口则走JdkDynamicAopProxy反之走ObjenesisCglibAopProxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对应我们也给出jdk动态代理的创建细节,本质上就是通过反射完成创建:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.........
//获取被代理的类的接口
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//生成代理对象并返回
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
2
3
4
5
6
7
8
9
10
而cglib实现也很预期说明的一致,通过ASM完成字节码增强:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.......
try {
.......
//将当前类信息通过enhancer 生成代理对象
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
//设置目标类元信息
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
//返回最终生成的代理对象
return createProxyClassAndInstance(enhancer, callbacks);
}
........
}
catch (Throwable ex) {
......
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 详解CGLIB代理
# Spring AOP和Cglib的关系
CGLIB是一个强大、高性能的代码生成包。使用ASM操作字节码,动态生成代理,对方法进行增强。,它广泛的被许多AOP框架使用,为他们提供方法的拦截。

例如我们希望对某个service进行日志打印,基于CGLIB我们可以这样实现:
前置步骤引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
2
3
4
5
首先创建用户类
public class User {
private String name;
private int age;
//get set
}
2
3
4
5
6
7
8
9
10
11
对应我们也给出一个serivice类的代码示例,即通过一个简单的死数据完成列表查询操作:
public class UserServiceImpl {
public List<User> findUserList() {
return Collections.singletonList(new User("xiaoming", 18));
}
}
2
3
4
5
6
7
对应代理实现逻辑如下:
public class CglibProxy<T> implements MethodInterceptor {
private static Logger logger = LoggerFactory.getLogger(CglibProxy.class);
private Object target;
public T getTargetClass(Object target) {
//设置被代理的目标类
this.target = target;
// 创建加强器设置代理类以及回调,当代理类被调用时,callback就会去调用intercept
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
//返回代理类
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
logger.info("调用被代理对象的方法,代理对象:[{}],代理方法:[{}]", o.getClass().getName(), method.getName());
Object result = methodProxy.invokeSuper(o, args);
logger.info("代理调用结束,返回结果:[{}]", String.valueOf(result));
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
测试代码
public class Main {
public static void main(String[] args) {
CglibProxy<UserServiceImpl> cglibProxy = new CglibProxy();
UserServiceImpl targetClass =cglibProxy.getTargetClass(new UserServiceImpl());
targetClass.findUserList();
}
}
2
3
4
5
6
7
8
9
10
# Cglib代理流程是是什么样的
如下图所示,整体来说就是基于enhancer去配置被代理类的各种参数,然后生成代理类:
注意:final方法无法被代理,因为它不可被子类覆盖。

# Spring中的Cglib代理流程
源码如下,我们可以看出和我们写的实例代码是差不多的。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.....
try {
//获取当前类信息获取生成代理对象
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
// 获取当前类中的方法
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 生成代理对象
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
.....
}
catch (Throwable ex) {
.....
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# JDK代理示例
JDK动态代理是JDK自带的一种代理机制,我们只需实现InvocationHandler接口即可。但是前提是被代理的类必须实现一个或多个接口才能使用JDK动态代理。
首先我们定义接口,User类沿用上述的
public interface UserService {
List<User> findUserList();
}
2
3
4
5
修改UserServiceImpl
public class UserServiceImpl implements UserService{
@Override
public List<User> findUserList() {
return Collections.singletonList(new User("xiaoming", 18));
}
}
2
3
4
5
6
7
8
jdk代理类
public class JDKProxy<T> {
private static Logger logger = LoggerFactory.getLogger(JDKProxy.class);
private Object target;
public JDKProxy(Object target) {
this.target = target;
}
public T getTargetObj() {
UserService proxy;
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{UserService.class};
InvocationHandler handler = (p, method, args) -> {
logger.info("代理方法被调用,类名称[{}],方法名称[{}]", target.getClass().getName(), method.getName());
Object result = method.invoke(target, args);
logger.info("代理方法调用结束,返回结果:[{}]", String.valueOf(result));
return result;
};
proxy = (UserService) Proxy.newProxyInstance(loader, interfaces, handler);
return (T) proxy;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
测试代码
public class Main {
public static void main(String[] args) {
JDKProxy<UserService> jdkProxy=new JDKProxy<>(new UserServiceImpl());
UserService userService = jdkProxy.getTargetObj();
System.out.println(userService.findUserList());
}
}
2
3
4
5
6
7
8
9
# JDK代理的工作原理
本质上,JDK动态代理也是在运行时定位获取目标,针对目标方法字节码逻辑前后写入增强逻辑,对外暴露代理调用,以我们上述findUserList为例,JDK代理会在运行时拦截该方法并将逻辑写入到字节码文件中,对外暴露一个代理的findUserList调用:

我们不妨在jvm在下面这样一段参数,将saveGeneratedFiles设置为 true 后,让生成的代理类会被保存到项目根目录,看看JDK代理在这期间做了什么事情:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
调试运行后,可以发现它回步入generateProxyClass方法,从语义我们大体可以猜测,它执行了:
- 创建代理
- 动态修改字节码
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
...
}
2
3
4
5
6
7
8
9
而代理方法做的事情,整体如下所示,可以看到它整体做的就是拿着被代理类的各种方法封装成ProxyMethod方法,然后写入class文件中:
/**
* Generate a class file for the proxy class. This method drives the
* class file generation process.
*/
private byte[] generateClassFile() {
/* 第一步:将所有方法包装成ProxyMethod对象 */
// 将Object类中hashCode、equals、toString方法包装成ProxyMethod对象
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 将代理类接口方法包装成ProxyMethod对象
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
//......
/* 第二步:为代理类组装字段,构造函数,方法,static初始化块等 */
try {
// 添加构造函数,参数是InvocationHandler
methods.add(generateConstructor());
// 代理方法
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 字段
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// 上述ProxyMethod中的方法
methods.add(pm.generateMethod());
}
}
// static初始化块
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
//......
/* 第三步:写入class文件 */
//......
try {
//......
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
return bout.toByteArray();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
看看上文命令下创建class,可以看到它 implements UserService 以及通过我们的的代理类的InvocationHandler 调用这些方法
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//......
//动态代理了findUserList方法,后续调用时本质上是通过代理类的method对原有方法进行调用,即我们的InvocationHandler所实现的逻辑
public final List findUserList() throws {
try {
return (List)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//......
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
//初始化我们的代理方法类method
m3 = Class.forName("com.pdai.aop.jdkProxy.UserService").getMethod("findUserList");
//......
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Spring AOP中JDK代理的实现
JdkDynamicAopProxy源码如下,可以看到本质上也是通过传入:
- 类加载器
- 接口类型,通过proxiedInterfaces获取对应接口到代理缓存中获取要生成的代理类型
- 对应的InvocationHandler 进行逻辑增强。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//......
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
2
3
4
5
6
# 代理性能对比与Spring Boot选择
在JDK8之前,CGLIB代理的方法调用效率确实优于JDK动态代理,但JDK动态代理的代理创建效率更高(参考《精通Spring4.x》一书及相关性能测试文章 (opens new window))。

从JDK8开始,JIT编译器对反射调用进行了大量优化(如内联优化),JDK动态代理的方法调用效率已与CGLIB相当。
以下是性能对比测试核心代码:
/**
* JDK动态代理 vs CGLIB代理 性能对比测试
*/
public class ProxyPerformanceTest {
private static final int CREATE_ITERATIONS = 100_000; // 代理创建次数
private static final int INVOKE_ITERATIONS = 10_000_000; // 方法调用次数
// 目标接口
public interface UserService {
String getUserName(Long userId);
}
// 目标实现类
public static class UserServiceImpl implements UserService {
@Override
public String getUserName(Long userId) {
return "User_" + userId;
}
}
// JDK动态代理的InvocationHandler
public static class JdkProxyHandler implements InvocationHandler {
private final Object target;
public JdkProxyHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
// CGLIB的MethodInterceptor
public static class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
}
// 测试代理创建效率
private static void testProxyCreation() {
UserServiceImpl target = new UserServiceImpl();
// JDK动态代理创建
long jdkStart = System.nanoTime();
for (int i = 0; i < CREATE_ITERATIONS; i++) {
UserService jdkProxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
new JdkProxyHandler(target)
);
}
long jdkDuration = System.nanoTime() - jdkStart;
// CGLIB代理创建
long cglibStart = System.nanoTime();
for (int i = 0; i < CREATE_ITERATIONS; i++) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CglibProxyInterceptor());
enhancer.create();
}
long cglibDuration = System.nanoTime() - cglibStart;
System.out.printf("JDK动态代理创建耗时: %,d ms%n", TimeUnit.NANOSECONDS.toMillis(jdkDuration));
System.out.printf("CGLIB代理创建耗时: %,d ms%n", TimeUnit.NANOSECONDS.toMillis(cglibDuration));
}
// 测试方法调用效率
private static void testMethodInvocation() {
UserServiceImpl target = new UserServiceImpl();
UserService jdkProxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
new JdkProxyHandler(target)
);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CglibProxyInterceptor());
UserService cglibProxy = (UserService) enhancer.create();
// JDK代理方法调用
long jdkStart = System.nanoTime();
for (int i = 0; i < INVOKE_ITERATIONS; i++) {
jdkProxy.getUserName((long) i);
}
long jdkDuration = System.nanoTime() - jdkStart;
// CGLIB代理方法调用
long cglibStart = System.nanoTime();
for (int i = 0; i < INVOKE_ITERATIONS; i++) {
cglibProxy.getUserName((long) i);
}
long cglibDuration = System.nanoTime() - cglibStart;
System.out.printf("JDK动态代理调用耗时: %,d ms%n", TimeUnit.NANOSECONDS.toMillis(jdkDuration));
System.out.printf("CGLIB代理调用耗时: %,d ms%n", TimeUnit.NANOSECONDS.toMillis(cglibDuration));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
测试结果(JDK 17环境):
【测试1】代理创建效率对比 (100000 次)
JDK动态代理创建耗时: 40 ms (0.000 ms/个)
CGLIB代理创建耗时: 39 ms (0.000 ms/个)
创建效率对比: JDK 0.98倍 慢于 CGLIB
2
3
4
从测试结果可以看到,在JDK8+环境中,JIT编译器通过方法内联优化(将反射调用优化为直接方法调用)后,JDK动态代理和CGLIB在代理创建效率上差异已经非常小,几乎可以忽略不计。
再来看方法调用效率的测试结果:
【测试2】方法调用效率对比 (10000000 次)
直接调用耗时: 85 ms
JDK动态代理调用耗时: 404 ms
CGLIB代理调用耗时: 311 ms
方法调用效率对比: JDK 0.77倍 慢于 CGLIB
相对于直接调用的开销: JDK 4.71倍, CGLIB 3.63倍
2
3
4
5
6
从测试结果可以看到,CGLIB在方法调用效率上仍然略高于JDK动态代理(约快23%),这也是Spring Boot 2.x选择CGLIB作为默认代理的原因之一,当然更重要的原因还是CGLIB不强制要求类实现接口,避免了类型转换异常问题。
以下是Spring Boot 2.x中AopAutoConfiguration的源码,印证了上述结论:
@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
// JDK动态代理配置:需要显式设置 spring.aop.proxy-target-class=false
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
static class JdkDynamicAutoProxyConfiguration {
}
// CGLIB代理配置:默认生效(matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true) // 关键:没有配置时默认使用CGLIB
static class CglibAutoProxyConfiguration {
}
}
// 当没有AspectJ依赖时,也默认使用CGLIB
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
从源码可以看到:
CglibAutoProxyConfiguration中的matchIfMissing = true表示没有配置时默认使用CGLIBJdkDynamicAutoProxyConfiguration需要显式设置spring.aop.proxy-target-class=false才会生效
# 小结
本文从源码层面深入剖析了Spring AOP的实现原理,核心要点如下:
- Spring AOP基于动态代理实现:目标类实现接口时使用JDK动态代理,否则使用CGLIB
- JDK8+性能优化:JIT编译器通过方法内联优化,使JDK动态代理与CGLIB性能差距缩小
- Spring Boot 2.x默认CGLIB:主因是不强制要求类实现接口,避免类型转换异常,而非单纯性能考量
- 五种通知类型:Before、After、AfterReturning、AfterThrowing、Around,各有适用场景
希望本文能帮助你不仅会用Spring AOP,更能理解其底层原理。
你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go、C 等多语言技术栈,现任某知名黑厂高级开发工程师,专注于高并发系统架构设计与性能优化。
🌟 开源项目贡献
- mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
🔗 https://github.com/shark-ctrl/mini-redis (opens new window)(欢迎 Star & Contribute)
📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。
👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!
# Spring AOP相关面试题精选
# 1. 以下关于AOP的说法,正确的是?
A. AspectJ是编译时织入,可以织入任何方法
B. Spring AOP基于代理模式,只能代理public方法
C. JDK动态代理要求目标类实现接口
D. CGLIB通过接口实现代理
正确答案:C
解析:
A ❌:AspectJ是编译时/类加载时织入,不是Spring AOP的方式
B ❌:Spring AOP只能代理public方法(表述有歧义,但不够准确)
C ✅:JDK动态代理要求目标类实现接口,通过反射创建代理
D ❌:CGLIB通过继承实现代理(子类化),不是接口
# 参考
Spring 常见面试题总结:https://javaguide.cn/system-design/framework/spring/spring-knowledge-and-questions-summary.html#spring-基础 (opens new window)
Spring进阶 - Spring AOP实现原理详解之AOP切面的实现:https://www.pdai.tech/md/spring/spring-x-framework-aop-source-1.html (opens new window)
Spring进阶 - Spring AOP实现原理详解之AOP代理的创建:https://www.pdai.tech/md/spring/spring-x-framework-aop-source-2.html (opens new window)
Spring进阶 - Spring AOP实现原理详解之Cglib代理实现:https://www.pdai.tech/md/spring/spring-x-framework-aop-source-3.html (opens new window)
Spring进阶 - Spring AOP实现原理详解之JDK代理实现:https://www.pdai.tech/md/spring/spring-x-framework-aop-source-4.html (opens new window)
《spring实战第四版》
- 01
- Windows 10 下的 Maven 安装配置教程05-11
- 02
- 基于 Claude Code 复刻 Redis 慢查询指令实践05-11
- 03
- VSCode与Claude Code后端开发环境搭建与AI编程工作流实践05-09