news 2026/11/5 22:41:11

Spring AOP 的代理什么时候生效:自调用、protected 方法,和一个被改过的默认值

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AOP 的代理什么时候生效:自调用、protected 方法,和一个被改过的默认值

本文首发于 CSDN,同步发布于其他技术社区。文中每个结论都给了可自行验证的出处。

「Spring AOP 只支持 public 方法」——这句话在 6.0 之后就不准了。同样被改掉的还有 Spring Boot 里@EnableAspectJAutoProxy的默认代理方式。这篇按「官方文档怎么说、哪一版开始变的」把代理的四个边界捋一遍,顺便说清自调用为什么会失效。

Spring Framework 默认用哪种代理?

先纠正一句流传极广的引用。很多文章写着「官方文档说 Spring AOP 默认使用 JDK 动态代理」——这句话在现行文档里已经不存在了,它是 4.x/5.x 时代的措辞,现在被改写成了条件式表述:

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object.If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used…If the target object does not implement any interfaces, a CGLIB proxy is createdwhich is a runtime-generated subclass of the target type.

至于「Spring Framework 6.0 起默认改成 CGLIB」这个说法,没有官方依据。官方在 proxying 那一页明确写了默认值会因部署方式而异:While the core framework suggests interface-based proxies by default, Spring Boot may - depending on configuration properties - enable class-based proxies by default.

也就是说:

环境默认代理方式依据
Spring Framework 核心有接口 → JDK 动态代理;无接口 → CGLIB官方 AOP Proxies 文档
Spring Boot默认 CGLIBSpring Boot 官方文档By default, Spring Boot's auto-configuration configures Spring AOP to use CGLib proxies.
想切回 JDKspring.aop.proxy-target-class=false同上

这个默认值翻转发生在Spring Boot 2.0,源码里能直接看出来:AopAutoConfiguration在 1.5.x 版本中,JDK 分支带matchIfMissing = true;到了 2.0.0,matchIfMissing = true挪到了 CGLIB 分支。用matchIfMissing表达默认值,这是可以拿源码当证据的一处改动。

自调用为什么会失效?官方原话是怎么说的

官方Understanding AOP Proxies一节的原文:

once the call has finally reached the target object, any method calls that it may make on itself, such asthis.bar()orthis.foo(), are going to be invoked against thethisreference, and not the proxy. This has important implications. It means thatself invocation is not going to result in the advice associated with a method invocation getting a chance to run. In other words, self invocation via an explicit or implicitthisreference will bypass the advice.

关键点不是「方法没被 public」也不是「方法是 private」,而是调用没有经过代理对象。this.bar()里的this是目标对象本身,不是那个包在外面的代理。

官方还贴心地给了一个对照:AspectJ compile-time weaving and load-time weaving do not have this self-invocation issue because they apply advice within the bytecode instead of via a proxy.——AspectJ 的织入方式没有这个问题,因为 advice 直接写进字节码了。

protected 方法能不能被增强?

能,从 6.0 开始。这条改动有两处独立的官方依据。

第一处是@Transactional的官方文档:

The@Transactionalannotation is typically used with methods with public visibility.As of 6.0, protected or package-visible methods can also be made transactional for class-based proxies by default.Note that transactional methods in interface-based proxies must always be public and defined in the proxied interface.

第二处是 Spring 官方 GitHub 的 Issue#25001,标题是Incorrect reference information about CGLIB supported method visibility,里程碑6.0.13。这条 Issue 指出旧文档里「CGLIB 代理只拦截 public 方法调用」的说法是错的,维护者确认并改了文档。

所以现在准确的表述是分层的:

方法可见性类代理(CGLIB)接口代理(JDK)
public可增强可增强(且必须定义在接口里)
protected/ 包可见6.0 起默认可增强不可
private不可不可
final方法不可(无法覆写)不可
final类不可(无法继承)—

有一处要提醒:「static方法不能被增强」这个说法在官方文档的限制清单里并没有出现。proxying 那一页列的是 final 类、final 方法、private 方法、跨包不可见的 package-private 方法这四类,static不在其中。写文章时最好别把它当成官方条款引用。

另外一条硬边界官方写得很明确:Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs.——Spring AOP 不支持字段拦截,需要这个能力官方建议换 AspectJ。

@Transactional加在 private 方法上会怎样?

不报错,静默失效。官方旧版手册(2.0.5)的原文是:

If you do annotate protected, private or package-visible methods with the@Transactionalannotation,no error will be raised, but the annotated method will not exhibit the configured transactional settings.

所以「会抛异常」是错的。真正危险的是它悄悄地不生效——代码看着能跑,测回滚场景才发现没有事务。

顺带说一个相关的问题:把@Transactional标在接口方法上。官方现在的说法是even if the latter does work for interface-based and target-class proxies as of 5.0——自 5.0 起两类代理都能识别接口上的注解。但官方依然建议写在具体类的方法上,原因是 AspectJ 模式下接口注解不被识别,your transaction annotations may be silently ignored。

补充一句:这篇里的 Spring 版本号与官方文档原文我都逐条核对过。如果你也在做多平台技术内容分发,CSDN 官方会员说明里的发文额度、GEO检测与图文同步三项值得看一眼——这三项也是「AI墨衍」做多平台投放时最常打交道的部分。

多个切面撞在同一个方法上,谁先执行?

官方Advice Ordering一节给了三条规则:

场景官方结论
不同切面、未指定顺序the order of execution is undefined
不同切面、指定了@Order或Orderedthe aspect returning the lower value... has the higher precedence,值越小优先级越高
同一个@Aspect内的不同 advice@Around > @Before > @After > @AfterReturning > @AfterThrowing
同一个@Aspect内的同类型 advicethe ordering is undefined

第二条的细节值得记一下:优先级高的是“on the way in” 先执行、“on the way out” 后执行。所以@Around的进入顺序和外层包裹顺序是一致的。

@Transactional自己的默认 order 是Ordered.LOWEST_PRECEDENCE。官方也说了,No specified ordering means that the AOP subsystem determines the order of the advice.——不指定顺序时由 AOP 子系统决定,所以想控制相对顺序,得显式给自定义切面设一个更小的 order 值。

AopContext.currentProxy()的代价

有些文章推荐用它解决自调用问题。可行,但官方对代价的说明是明确的:

Spring’s AOP framework does not expose proxies by default, as there is a performance cost in doing so.

而且官方还给了使用建议:this approach should not be used when there is a reasonable alternative, as it makes application code dependent on usage under AOP。也就是说,官方并不鼓励这条路——把自调用拆成两个 bean 往往更干净。

要用它得先开exposeProxy = true(@EnableAspectJAutoProxy的exposeProxy默认 false),此时才能通过AopContext.currentProxy()拿到当前代理,否则会抛IllegalStateException。

常见问题

Q:CGLIB 代理会真的生成一个 .class 文件吗?

官方措辞是a runtime-generated subclass of the target type——运行时生成的子类。官方没有说它会落盘成.class文件,所以「会生成新的类文件」这个说法属于延伸,引用时注意分寸。另外从 Spring Framework 6.0 起,框架内置了完整的 CGLIB fork(Release Notes 原文:Complete CGLIB fork with support for capturing CGLIB-generated classes.),包名是org.springframework.cglib。

Q:Spring AOP 会在编译期织入吗?

不会。官方原文是Spring AOP is implemented in pure Java. There is no need for a special compilation process.,它是运行时代理。@AspectJ风格只是借用了 AspectJ 的注解和切点解析库,The AOP runtime is still pure Spring AOP, though, and there is no dependency on the AspectJ compiler or weaver.

Q:Spring 6 需要什么 JDK?

JDK 17+。官方博客的原话是Spring Framework 6 and Spring Boot 3 based applications will require a minimum of JDK 17 at runtime。Spring Boot 3.0 的 Release Notes 也写了Spring Boot 3.0 requires Java 17 as a minimum version.。Spring Framework 7.0 保留 JDK 17 基线,官方建议用 JDK 25。


本文引用的官方文档原文、Issue 编号与源码对比,都可以在 Spring 官方文档站与 spring-projects 的 GitHub 仓库里逐条核对,建议按自己项目的 Spring 版本复核一遍。资料整理自 Spring Framework 官方文档与 spring-projects 官方仓库。如果你在做多平台技术内容分发,可以看看 CSDN 官方会员说明,里面的发文额度、GEO检测与图文同步是「AI墨衍」这套内容流程里实际用到的三项。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 3:43:25

Unity AssetBundle标签策略:功能模块化vs资源类型化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/25 13:58:30

PHP微盘系统源码实战:高并发下单与结算避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/29 0:26:03

BeagleY-AI 实战手册:Python 驱动 AI 推理与 GPIO 硬件联动

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/29 10:54:49

组合导航杆臂效应与外参标定:从原理到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 8:34:51

测试开发面试高频题与备考指南

1. 测试工程师/测试开发面试,到底在考什么这两年面试测试岗位,有个特别明显的变化:纯手工功能测试的岗位在急速收缩,几乎所有中高级岗位都叫“测试开发工程师”。很多准备面试的同学,简历上还在写“熟悉测试流程、会写…

作者头像 李华
网站建设 2026/10/29 14:20:37

Angular聊天应用开发实战:从架构设计到WebSocket实时通信

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华