> 文档中心 > 【方向盘】Spring Boot 2.6.0正式发布,循环引用终于被禁

【方向盘】Spring Boot 2.6.0正式发布,循环引用终于被禁


天再高又怎样,踮起脚尖就更接近阳光。
本文已被https://yourbatman.cn收录;女娲Knife-Initializr工程可公开访问啦;程序员专用网盘https://wangpan.yourbatman.cn;技术专栏源代码大本营:https://github.com/yourbatman/tech-column-learning;公号后台回复“专栏列表”获取全部小而美的原创技术专栏

你好,我是方向盘(YourBatman)。笔者的公号是保留地,只分享原创,不转载、不发商务广告!

✍前言

北京时间2021-11-17,Spring Boot 2.6.0正式发布。回忆一下上次发版还是上次,相比于2.5.0版本的打酱油,本次的升级点更猛些。

2.5.0版本的新特性在这里:【方向盘】Spring Boot 2.5.0正式发布,环境变量可指定前缀的功能很赞)

说明:Spring Boot 2.6.1随后作为补丁版本立马发布了,修复了若干问题。因此保持习惯,生产上请尽量保持最新的(小)版本

所属专栏

  • 【方向盘】-Spring Boot新特性

相关下载

  • 【本专栏源代码】:https://github.com/yourbatman/FXP-java-ee
  • 【技术专栏源代码大本营】:https://github.com/yourbatman/tech-column-learning
  • 【女娲Knife-Initializr工程】访问地址:http://152.136.106.14:8761
  • 【程序员专用网盘】公益上线啦,注册送1G超小容量,帮你实践做减法:https://wangpan.yourbatman.cn
  • 【Java开发软件包(Mac)】:https://wangpan.yourbatman.cn/s/rEH0 提取码:javakit

版本约定

  • Spring Boot 2.6.0

✍正文

关于版本号,从2.4.x 版本开始版本号不带 .RELEASE 后缀了!通过表格描述下Spring Boot各个版本现在的更新、维护状况:

版本 发布时间 停止支持时间 停止商业支持时间
2.6.x 2021-11-17 2022-11-24(1年) 2024-02-24(2年+)
2.5.x 2021-05-20 2022-05-19(1年) 2023-08-24(2年+)
2.4.x 2021-11-12 2022-11-18(1年) 2023-02-23(2年+)
2.3.x 2020-05-16 2022-05-20(1年) 2022-01-16(2年+)
2.2.x 2019-10-16 2020-10-16(1年) 2021-01-30(2年+)
2.1.x 2018-10-30 2019-10-30(1年) 2021-01-30(2年+)
2.0.x 2018-03-01 2019-03-01(1年) 2020-06-01(2年+)
1.5.x 2017-01-30 2019-08-06(2.5年) 2020-11-06(近4年)

Spring Boot每年会在5月份和11月份发布两个中型版本(一般都会有部分不向下兼容的情况,升级需谨慎),每个中型版本提供1年的支持(免费),提供2年+的商业支持(付费)。按此节奏可知:Spring Boot 2.6.0发布也宣布着2.4.x版本停止(免费)支持,而2.7.0版本预计会在2022年的5月份和大家见面。

2.6版本主要新特性

✌禁止循环引用

Spring Boot终究忍不住,禁止(Bean的)循环引用了!!!

注意:只是Spring Boot默认禁止了,但Spring Framework默认还是允许的哦

对于有代码洁癖的开发者来说,看到循环引用的代码是“不舒服”的。在业务开发中,有一种声音是:循环引用不可避免,但实际上应该思考:若出现了循环引用,必定是结构设计上不合理导致,有优化空间!若你是个有追求的程序员,是可以很容易发现这种不合理的。

什么是循环引用
在这里插入图片描述
如图,循环引用一般指A引用B,B又引用了A。更极端一点的循环引用case可以是:A引用A,本文将以此为例进行代码演示。

什么是循环依赖?它是循环引用的一种具象形式,如Spring Bean之间的循环依赖就属于循环引用。大多数情况下,可认为循环依赖和循环引用语义上是相同的

在Spring Boot场景下,准备Bean循环依赖的基础代码:

/** * 在此处添加备注信息 * * @author YourBatman. Send email to me * @site https://yourbatman.cn * @date 2021/12/11 20:43 * @since 0.0.1 */@Servicepublic class AService {    @Autowired    private AService aService;    @PostConstruct    private void init() { System.out.println("循环依赖:" + (this == aService));    }}

2.6.0之前版本(以2.5.x为例)

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.5.7</version></parent>

启动Spring Boot应用,控制台输出:
在这里插入图片描述
结果:正常启动。这便是我们口头上常说的:Spring已经解决了Bean的循环依赖问题

2.6.0及之后版本

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.6.0</version></parent>

启动Spring Boot应用,控制台输出:
在这里插入图片描述
结果:启动失败。这便是从Spring Boot 2.6.0版本起禁止了循环引用的结果

如何解决循环引用?

文上有说到,循环引用属于不合理的设计,但并非不能正常工作。这就像每个程序员都吐槽过屎山代码依旧能正常work同一个道理:它不好,但有意义。

既然“不合理”,那就有理由规避。针对循环引用的解决方案,总结一下主要有两种:

  1. 确保循环引用不再存在:整改/优化业务逻辑
  2. 允许循环引用:无需改代码
方案一:确保循环引用不再存在

好,这很好!难,这很难!本方案是最好的,也是最难的,Spring团队当然最喜欢你这么去做,做难事必有所得嘛!

从Spring Boot 2.6.0开始的这个默认行为(不允许循环引用)能感受到:循环引用的编码方式是不被推荐的,是坏味道的代码。为此,期望正在看本文的coder给自己立个flag哈:不再写循环引用的代码,尽量吧😄。

奈何,好的东西/方案实现起来一般都很难,循环引用亦是如此。在笔者认为难点主要在程序员本身,主要表现在这三点:

  1. 思考不足。提起需求就开工看起来效率很高,实则往往相反
  2. 眼光不远。这是短期利益和长期收益的PK,短期利益更具诱惑性,然而长期收益才具备更高价值
  3. 追求不够。明明知道这么做不太好,但就是这么做了。克服困难好比打怪升级,过关斩将方能提高自己的上限

在这里插入图片描述
从A点到B点,若距离只有10m,走路的方式是最快的;若有1km,自行车是最佳;若超过10km,就是小汽车;若超过1000km,当选火车/飞机!总而言之:能够积累才叫多,不用重来才叫快!

方案二:允许循环引用

此方案更像是绕过问题而非解决问题本身!!!

它是一种妥协方案而非最佳实践。在Spring Boot 2.6.0之前版本无需担心此问题(默认允许循环引用),若你准备使用2.6.x但现实情况依旧必须允许循环引用那该怎么办呢?

有哪些现实情况呢?诸如:老项目升级Spring Boot版本需要保持向下兼容性;公司coder的水平不一,强制高标准的要求将会严重影响到生产效率等等

为此,做法只有一个:禁用默认行为(允许循环引用)。具体做法也很简单,其实在文上启动失败的报错详情里Spring Boot已非常贴心的告诉你了:
在这里插入图片描述
所以只需在配置文件application.properties里加上这个属性:

spring.main.allow-circular-references = true

再次启用Spring Boot 2.6.0版本的应用:正常启动。

除了加属性这个方法之外,也可以通过启动类API的方式来设置,能达到同样效果:

public static void main(String[] args) {    new SpringApplicationBuilder(Application.class)   .allowCircularReferences(true) // 允许循环引用   .run(args);}

在这里插入图片描述
我们知道,允许循环引用与否其实是Spring Framework的能力,Spring Boot只是将其暴露为属性参数方便开发者来控制而已。那么问题来了,如果是一个构建在纯Spring Framework上的应用,如何禁止循环引用呢?你知道怎么做吗?欢迎在留言区讨论作答,或私聊我探讨学习~


加餐:允许循环引用了但依旧报错

也许你一直认为Spring已经解决循环引用问题了,所以在使用过程中可以“毫无顾忌”。非也,某些“特殊”场景下可能依旧会碰壁,并且问题还很隐蔽不好定位,不信你看我层层递进的给你描述这个场景:

说明:以下代码在允许循环引用的Spring Boot场景下演示运行

基础代码:

本例使用@PostConstruct来模拟触发方法调用,效果和Controller里调Service方法一样哈

@Servicepublic class AService {    @PostConstruct    private void init() { String threadName = Thread.currentThread().getName(); System.out.printf("线程号为%s,开始调用业务fun方法\n", threadName); fun();    }    public void fun() { String threadName = Thread.currentThread().getName(); System.out.printf("线程号为%s,开始处理业务\n", threadName);    }}

启动应用即触发动作,控制台输出为:

线程名为main,开始调用业务fun方法线程名为main,fun方法开始处理业务

完美!此时,你发现fun方法执行时间太长,需要做异步化处理。你就立马想到了使用Spring提供的@Async注解轻松搞定:

@Asyncpublic void fun() {...}

再次运行,控制台输出:

线程名为main,开始调用业务fun方法线程名为main,fun方法开始处理业务

what?木有生效呀!这时你灵机一动,原因是没用开启该模块嘛。所以你迅速的使用@EnableAsync注解启用Spring的异步模块,满怀期待的再次运行应用,控制台输出:

线程名为main,开始调用业务fun方法线程名为main,fun方法开始处理业务

what a …?怎么还是不行。你挠了挠头,想起来之前踩过的“事务不生效的坑”,场景和这类似,所以你模仿着采用了相同的方式来解决:自己注入自己(循环依赖)

@Autowiredprivate AService aService; // 自己注入自己@PostConstructprivate void init() {...    aService.fun(); // 通过代理对象调用而非this调用}

这次满怀信心的再次运行,没想到,启动抛出BeanCurrentlyInCreationException异常

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [AService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649) ~[spring-beans-5.3.13.jar:5.3.13]...

异常关键字:circular reference循环引用!!!不是说好了允许循环引用的吗?怎么肥四?怎么破???

至此,笔者将此问题抛出,有兴趣的同学可思考一下问题根因、解决方案哈。最终的效果应该是不同线程异步执行的:

线程名为main,开始调用业务fun方法线程名为task-1,fun方法开始处理业务

Tips:笔者在之前的文章里对此问题有过非常非常详细的叙述,感兴趣的可自行向前翻哈!!!主动学习😄

✌更加灵活的自定义脱敏规则

对于/env/configprops这两个端点,常常会有敏感信息存在,比如:数据库密码等等。为了避免敏感信息外泄,一般做法是禁用这两个端点,但粒度太粗,在很多时候是不合适的,因为这可能大大增加调试程序、定位问题的复杂程度,所以对该端点的某些信息脱敏不失为一个折中的好办法。

Spring Boot使用Sanitizer(中文意思:消毒杀菌剂)来进行脱敏。比如属性配置有如下配置:

mysql.password = 123456redis.pwd = 654321

这时候访问端点/actuator/env,得到的结果是这样子的:
在这里插入图片描述
如图所示,感觉有点厚此薄彼有木有???其实一切事出有因,EnvironmentEndpoint使用Sanitizer进行脱敏处理,而它自带一些默认行为:
在这里插入图片描述
若不再这个范围内的key(比如上面的redis.pwd)也需要脱敏,很简单,价格配置项即可:

management.endpoint.env.additional-keys-to-sanitize = redis.pwd#management.endpoint.env.additional-keys-to-sanitize = pwd # 脱敏范围更大

效果如下:
在这里插入图片描述
完美脱敏!!!这么做可以搞定绝大部分场景,但是某些特殊情况下,通过这种配置不是很好做,比如:同一个key,在不同的属性源里表现不一样。在application.properties里的话脱敏,而在application-dev.properties里不需要脱敏(开发环境嘛,明文裸奔更有助于调试程序)。

这个case若适用上面配置的方式不可处理,确切点说很不方便吧。Spring Boot意识到了这个“难点”,在2.6.0版本了新增了更灵活的自定义脱敏规则的能力,做法很简单:自定义SanitizingFunction类型的Bean即可。

// Since: 2.6.0@FunctionalInterfacepublic interface SanitizingFunction {SanitizableData apply(SanitizableData data);}

比如关于Redis的配置项放redis.properties文件里,然后读进来:

@PropertySource("classpath:redis.properties")@Configuration(proxyBeanMethods = false)public class AppConfiguration {}redis.properties文件内容:redis.pwd = 654321

要求:redis.properties文件里面所有包含pwd的key的值都做脱敏处理,而其它属性源不管。这时使用上面配置方式就无法实现了(或者说很难实现吧),Spring Boot 2.6.0新增的特性,API方式可以非常灵活方便的搞定:

@Beanpublic SanitizingFunction pwdSanitizingFunction() {    return data -> { org.springframework.core.env.PropertySource<?> propertySource = data.getPropertySource(); String key = data.getKey();// 仅对redis.properties里面的某些key做脱敏 if (propertySource.getName().contains("redis.properties")) {   if (key.equals("redis.pwd")) {return data.withValue(SANITIZED_VALUE);   } } return data;    };}

再次请求/actuator/env端点,结果如下:
在这里插入图片描述

✌Spring MVC默认使用全新匹配策略

在Spring Framework 5之前,关于路径匹配一直以来有且只有一种方式:基于Ant风格的url匹配,也就是熟悉的AntPathMatcher。在5.0版本之后引入了全新的路径匹配器:PathPattern

关于它俩都啥意思,怎么用,有什么区别,不是本文的重点。笔者前面文章有详细介绍,建议阅读哈。这里给个电梯直达:Spring5新宠:PathPattern,AntPathMatcher:那我走?

Spring Boot从2.0.0版本开始构建在Spring Framework 5之上,但它直到2.6.0版本才彻底的将Spring MVC的默认匹配从AntPathMatcher切换为了PathPattern,这也是本次版本升级的一大特征之一。代码上体现在这里:

// 2.5.7public static class Pathmatch {private MatchingStrategy matchingStrategy = MatchingStrategy.ANT_PATH_MATCHER;}// 2.6.0public static class Pathmatch {private MatchingStrategy matchingStrategy = MatchingStrategy.PATH_PATTERN_PARSER;}

若你需要回到Ant的匹配方式上(比如担心兼容性),只需加上一行简单配置就成:

spring.mvc.pathmatch.matching-strategy = ant-path-matcher

✌Redis自动开启连接池

现在,只要classpath里存在commons-pool2这个jar,就会自动为Redis开启连接池(包括Jedis和Lettuce哦)。

在2.6.0之前的版本,配置Redis时是否启用连接池是由使用者显示来决定的,现在自动了,说明Spring Boot是推荐使用Redis时用连接池的哦。

从源代码的角度,区别主要在这(以现在更为常用的Lettuce为例):

LettuceConnectionConfiguration
在这里插入图片描述
下面代码是2.6.0版本做的改动:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到策略是有变化的:之前默认关闭连接池需要显示开启,2.6.0之后是默认开启需要显示关闭

✌Spring Boot 2.4.x停止维护

按照Spring Boot现在版本规则:官方只免费维护当前主线版本和次版本,发布新版本后上上个版本自然就停止维护喽,倒逼开发者保持升级,用新版本产品,享受技术红利呀!

说明:这里指的停止维护是官方免费维护,不包含商业付费维护

✌依赖升级

这部分一般不用太关心,稍微留一下主要的组件版本即可。

  • Spring Data 2021.1
  • Spring Kafka 2.8
  • Apache Kafka 3.0(Spring果然站在最前沿呀)
  • Commons Pool 2.11
  • Elasticsearch 7.15
  • Hibernate 5.6
  • Mockito 4.0

✌删除和弃用

按照规约,在Spring Boot 2.4.0里被标注为弃用@Deprecated的类在此版本将会被删除。回忆2.4.0版本弃用了哪些?
在这里插入图片描述
Spring Boot 2.4.0最大升级就是对ConfigFileApplicationListener的升级。

电梯直达:Spring Boot 2.4.0正式发布,全新的配置文件加载机制(不向下兼容)

那时壮志雄心计划下下个版本(也就是2.6.0版本)就可以移除此类,但Spring团队这次还是担心步子迈得太大扯着dan,留下了它并改口将在3.0里移除掉。
在这里插入图片描述
弃用类:

  • JDBC的AbstractDataSourceInitializer体系,使用DataSourceScriptDatabaseInitializer体系替代
  • Hibernate的SpringPhysicalNamingStrategy,使用CamelCaseToUnderscoresNamingStrategy替代
  • 测试框架的AbstractApplicationContextRunner类的几个方法被启用,使用新的RunnerConfiguration类替代

✌官网新增SUPPORT标签页

由于Spring Boot的更新迭代速度非常快,每个版本的发版时间、维护周期一直困扰着广大开发者,为此随着2.6.0版本的发布,官网上非常暖心的提供了一个SUPPORT标签来展示各个版本的情况:
在这里插入图片描述
以及当天所处的一个状态:
在这里插入图片描述
地址:https://spring.io/projects/spring-boot#support

✍总结

Spring Boot 2.6.0的更新点还是比较多的,值得肯定,当然也值得升级。

Java领域的云原生时代,虽然受到了挑战,但毫无疑问在未来的5年甚至10年,Spring Boot依旧是标准的脚手架,是云原生应用的基础设施。它的能力能解放开发者的精力,时间用于业务设计、开发上。

最后,多分享一句。笔者从中觉得的每次版本升级符合Spring的决策哲学:先服从,再引领。毕竟对于庞大的Spring体系来说,每个重要决策都并非拍脑袋就可以,背后需要宏观思想作为指导。

拿循环引用这个例子来讲,Spring Framework最初默认允许循环依赖:设计上似乎留下了“不和谐”,但那会Spring初出茅庐,话语权不够,所以拥抱大众,活下来才是第一位。Spring技术栈发展到现在成为了实际的开发标准,在Java领域可谓已有绝对的话语权,因此它开始引领:默认不允许循环引用。

本专栏上下文

  • 【方向盘】Spring Boot 2.5.0正式发布,环境变量可指定前缀的功能很赞
  • 【方向盘】Spring Boot 2.4.0正式发布,全新的配置文件加载机制(不向下兼容)

推荐阅读

  • 【方向盘】每人送1G超小容量,“BAT网盘”免费助你做减法
  • 【方向盘】蚂蚁金服上市了,我不想努力了
  • 【方向盘】因“双减”失业,厉经9面,终获美团外卖L8的Offer

在这里插入图片描述

我是方向盘(YourBatman):一个前25年还不会写Hallo World、早已毕业的大龄程序员。网瘾失足、清考、延期毕业、房产中介、保险销售、送外卖…是我不可抹灭的黑标签

  • 👉🏻2006 - 2009:游戏《梦幻西游》骨灰玩家
  • 🎓2013.07 清考、毕业答辩3次未通过、延期毕业
  • 🏷2013.08-2014.07 宁夏中介公司卖二手房1年,毕业后第1份工作
  • ️️🏷2014.07-2015.05 荆州/武汉,泰康人寿卖保险3月、饿了么送外卖2月,还有炸鸡排、直销等第2345份工作
  • 🏷2015.08 开始从事Java开发,闯过外包,呆过大厂!多年架构经验,任基础架构/中间件团队负责人
  • 🏷2021.08 因“双减政策”失业!历经9面,终获美团外卖L8的offer
  • 🙅🏻‍♀️Java架构师、Spring开源贡献者、CSDN博客之星年度Top 10、领域建模专家、写作大赛1/2届评委
  • 📚将出版书籍《Spring奇淫巧技》,致力于国内zui好、zui具深度、zui实用的专栏,为市场贡献微薄之力
  • 现在写纯粹技术专栏(公号后台回复专栏列表),不哗众取宠。如果你也有共鸣,可加我好友(fsx1056342982)一起进步
序号 专栏名称 简介
01 【方向盘】-程序人生 程序人生,人生程序
02 【方向盘】-资讯/新特性 IDEA、JDK、Spring技术栈…新特性
03 【方向盘】-IntelliJ IDEA 熟练使用IDEA就相当拥有物理外挂,助你高效编码
04 【方向盘】-Bean Validation 熟练掌握数据校验,减少90%的垃圾代码
05 【方向盘】-日期时间 帮你解决JDK Date、JSR 310日期/其实 的一切问题
06 【方向盘】-Spring类型转换 Spring类型转换-框架设计的基石
07 【方向盘】-Spring static static关键字在Spring里的应用
08 【方向盘】-Cors跨域 关于跨域请求问题,本专栏足矣
09 【方向盘】-Jackson Almost Maybe是最好的Jackson专栏
10 【方向盘】-Spring配置类 专讲@Configuration配置类,你懂的
11 【方向盘】-Spring技术栈 暂无所属小分类的,Spring技术栈大分类
12 【方向盘】-JDK 暂无所属小分类的,JDK技术栈大分类
13 【方向盘】-Servlet Servlet规范、Web相关内容专题
14 【方向盘】-Java EE 从Java EE到Jakarta EE,30年弹指一挥间
15 【方向盘】-Spring Boot新特性 Spring Boot中、大版本发布时的新特性系列专题
16 【方向盘】-Spring Framework新特性 Spring Framework中、大版本发布时的新特性系列专题
17 【方向盘】-Spring Cloud新特性 Spring Cloud中、大版本发布时的新特性系列专题
99 源代码库 大多数专栏均配有源代码,都在这里
  • 源代码库地址:https://github.com/yourbatman/tech-column-learning
  • CSDN主页:https://blog.csdn.net/f641385712
  • 掘金主页:https://juejin.cn/user/430664289367192
  • 博客园主页:https://www.cnblogs.com/yourbatman
  • 个人博客主页:https://yourbatman.cn
  • 个人网盘主页:https://wangpan.yourbatman.cn