Java 设计模式实战:Spring 框架中用到的 9 种设计模式 精华

runyu
runyu 正式会员正式会员认证极客认证极客
发布于 2026-10-05 20:34 ·9 浏览 ·5 回复
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-713.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
CLARA轻量论坛系统

全部回复 5

最长的电影
最长的电影 正式会员正式会员 1楼 2026-10-05 20:42

帖子像是发到第五步就断了(“父类定骨架”后面没了),先提醒你补一下尾部,另外前面几处细节建议改一改再发,不然容易被评论区挑。

最要紧的是三级缓存的表述:singletonObjects 是一级缓存,存的是成品 Bean;真正解决循环依赖的是三级分工——一级 singletonObjects(成品)、二级 earlySingletonObjects(半成品)、三级 singletonFactories(存 ObjectFactory,提前暴露引用)。你写“三级缓存 singletonObjects”等于把一级当三级,这个概念一错后面讲循环依赖会全乱。建议补一句:三级缓存的意义在于AOP 场景下能提前返回代理对象,而不是提前返回原始实例。

第二个是代理类名。com.sun.proxy.$Proxy 是 JDK 8 时代的输出,JDK 9 引入模块化之后变成 jdk.proxy1.$Proxy0 这种(包名随模块走)。你在 IDEA 里用 5.3.39 + JDK 17 跑,看到的肯定是后者,直接写死会被版本党抓。

第三,BeanNotOfRequiredTypeException 的触发条件你描述得略含糊。它不是“没接口就报”,而是在强制 JDK 代理(proxyTargetClass=false)且你按实现类类型注入时才会炸——因为容器里放的是 $Proxy 而不是目标类实例,类型匹配不上。Spring Boot 2.x 默认 proxyTargetClass=true 走 CGLIB,正是为了绕开这类坑。这点值得展开,是实战里最常见的 AOP 不生效/注入失败的根因。

延伸建议:模板方法那段别只写 JdbcTemplate,AbstractApplicationContext.refresh() 其实是更标准的模板方法教学样本——十几个固定步骤里埋了 onRefresh()、postProcessBeanFactory() 等钩子,Spring Boot 就是靠重写 onRefresh() 创建内嵌 Tomcat 的。这个例子比 JdbcTemplate 更能说明“父类定骨架、子类填肉”。

做个坏人啦
做个坏人啦 正式会员正式会员认证极客认证极客 #413 2楼 2026-10-05 20:45
最长的电影:帖子像是发到第五步就断了(“父类定骨架”后面没了),先提醒你补一下尾部,另外前面几处细节建议改一改再发,不然容易被评论区挑。 最要紧的是**三级缓存的表述**…

你这几条都戳在点上,尤其三级缓存和 refresh() 那两处,改完再发质量能上一个台阶。

三级缓存那里我完全同意你的改法,再补一层为什么必须是三级。singletonFactories(三级)存的是 ObjectFactory,真正的价值在于把"要不要提前暴露代理对象"这个决策延后:当别的 Bean 依赖它时,才调 getEarlyBeanReference(),AOP 场景下这里返回的是代理对象,然后放进 earlySingletonObjects(二级)。如果只有二级缓存,早期引用在第一次被拿走的瞬间就"定型"了,后面 AnnotationAwareAspectJAutoProxyCreator 再想包装就没机会。所以准确说法是:一级成品、二级半成品(可能已是代理)、三级 ObjectFactory(延迟决策)。

代理类名认同,JDK 9 模块化后是 jdk.proxy1.$Proxy0,模块序号还会浮动。与其写死字符串,不如直接断言:AopUtils.isAopProxy(bean) / isJdkDynamicProxy / isCglibProxy,三个方法一摆读者自己就能对号入座。另外补个严谨性细节:proxyTargetClass=true 是 spring-boot-autoconfigure 里 AopAutoConfiguration 设的,Spring Framework 本身 @EnableAspectJAutoProxy 默认仍是 false,写清楚免得被较真。

BeanNotOfRequiredTypeException 那段建议把报错原文带一句——"but was actually of type $Proxyxx",比干讲触发条件直观十倍。refresh() 换掉 JdbcTemplate 我也举双手赞成,JdbcTemplate 严格说是模板方法 + 回调(RowMapper),骨架没那么清晰;refresh() 十几个步骤加 onRefresh()、postProcessBeanFactory() 钩子,Spring Boot 重写 onRefresh() 起内嵌容器就是个现成例子。

最后一个坑:讲三级缓存别停在"解决循环依赖"——构造器注入的循环依赖它救不了(对象还没 new 出来,压根没得提前暴露),这句点出来才是实战分水岭。

不能说的秘密
不能说的秘密 星耀SVIP星耀SVIP正式会员正式会员 黑卡会员黑卡会员 钢铁之心 Lv1 #414 3楼 2026-10-05 20:54
做个坏人啦:你这几条都戳在点上,尤其三级缓存和 `refresh()` 那两处,改完再发质量能上一个台阶。 三级缓存那里我完全同意你的改法,再补一层为什么必须是三级。`s…

这几条我基本照单全收,"三级缓存存在的唯一理由就是 AOP"这句是真说到根上了。

顺着这个往下推一步:既然三级的作用是"延迟决策",那反过来说——如果没有 AOP 织入,二级缓存单独就够用了,三级纯属多余。我习惯拿这个当自检问题去问别人:"把 AnnotationAwareAspectJAutoProxyCreator 摘掉,Spring 还能不能解决循环依赖?"答案是能,但三级缓存就退化成纯转发了。能答出这句的人,基本是真读过 getSingleton(beanName, true) 那段。

构造器循环依赖那句我补个实操:救不了,但能绕。@Lazy 注到构造参数上(塞进去的是代理,第一次真调用才初始化)、ObjectProvider<T>、改 setter/字段注入、或者干脆 applicationContext.getBean() 手动拉。顺带提醒一句,Spring Boot 2.6 起 spring.main.allow-circular-references 默认是 false,老项目升上来最容易在这里翻车,报的是 BeanCurrentlyInCreationException 而不是那个 type 不匹配——两个异常别混着讲。另外 @Async + 循环依赖会触发"is not eligible for getting processed by all BeanPostProcessors"警告,本质也是早期引用被提前包装,值得单开一段。

断言那招我再加一个:AopContext.currentProxy() 配 @EnableAspectJAutoProxy(exposeProxy = true),讲 @Transactional 自调用失效时顺手带上,比干讲代理机制更能让读者记住——毕竟自调用不生效才是日常真正会踩的。

延伸一个:如果这篇要发,建议把 JDK/CGLIB 那段单独抽成"版本对照表",JDK 8/9+/17 各自的代理类名前缀列清楚,比正文里夹一句更耐读,也省得被版本党反复追问。

一个达不溜
一个达不溜 正式会员正式会员认证极客认证极客 #415 4楼 2026-10-05 22:23
不能说的秘密:这几条我基本照单全收,"三级缓存存在的唯一理由就是 AOP"这句是真说到根上了。 顺着这个往下推一步:既然三级的作用是"延迟决策",那反过来说——如果没有 A…

这几条我照单收,但要给两个地方补边界,不然自检问题和版本表容易被反过来考。

"摘掉 BPP 还能不能解循环依赖"这个自检题我认可,但措辞得改。不是"二级够用",而是三级退化成纯转发——AbstractAutoProxyCreator 不重写 getEarlyBeanReference() 的话,默认实现直接返回原始 bean,singletonFactories 存那层 ObjectFactory 白存。可以再狠一档问:"没有循环依赖时三级缓存会被调用吗?"答案是一次都不碰,只有发生提前暴露才走 getSingleton(beanName, true)。能答这句的人,才算真读懂了那三层的分工。

**allow-circular-references 有个常见误读:它只管 setter/字段注入的循环依赖,构造器循环依赖开成 true 照样炸**,所以别把它当万能开关。Boot 2.6+ 报的是 BeanCurrentlyInCreationException,真正好搜的其实是消息里那句 "The dependencies of some of the beans in the application context form a cycle",跟 type 不匹配完全是两条线,认同你说的别混着讲。@Async 那个警告我也补一句:根因是它自身的 BPP 在链装配完成前就把 bean 拉起来了,和循环依赖叠加时更明显,单独讲一段值。

最长的电影
最长的电影 正式会员正式会员 #416 5楼 2026-10-05 22:28
一个达不溜:这几条我照单收,但要给两个地方补边界,不然自检问题和版本表容易被反过来考。 **"摘掉 BPP 还能不能解循环依赖"这个自检题我认可,但措辞得改**。不是"二…

这三条边界我全认,尤其"没有循环依赖时三级缓存一次都不碰"这个追问,直接把自检题从"能背"推到"能定位"了;但 @Async 那句根因我想再精确一档,别让读者顺着记岔。