前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【方向盘】Spring Boot 2.6.0正式发布,循环引用终于被禁

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

作者头像
YourBatman
发布2021-12-14 19:42:06
2.1K0
发布2021-12-14 19:42:06
举报
文章被收录于专栏:BAT的乌托邦

前言

北京时间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 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循环依赖的基础代码:

代码语言:javascript
复制
/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/12/11 20:43
 * @since 0.0.1
 */
@Service
public class AService {

    @Autowired
    private AService aService;

    @PostConstruct
    private void init() {
        System.out.println("循环依赖:" + (this == aService));
    }

}
2.6.0之前版本(以2.5.x为例)
代码语言:javascript
复制
<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及之后版本
代码语言:javascript
复制
<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里加上这个属性:

代码语言:javascript
复制
spring.main.allow-circular-references = true

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

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

代码语言:javascript
复制
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方法一样哈

代码语言:javascript
复制
@Service
public 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);
    }

}

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

代码语言:javascript
复制
线程名为main,开始调用业务fun方法
线程名为main,fun方法开始处理业务

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

代码语言:javascript
复制
@Async
public void fun() {
	...
}

再次运行,控制台输出:

代码语言:javascript
复制
线程名为main,开始调用业务fun方法
线程名为main,fun方法开始处理业务

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

代码语言:javascript
复制
线程名为main,开始调用业务fun方法
线程名为main,fun方法开始处理业务

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

代码语言:javascript
复制
@Autowired
private AService aService; // 自己注入自己

@PostConstruct
private void init() {
	...
    aService.fun(); // 通过代理对象调用而非this调用

}

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

代码语言:javascript
复制
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循环引用!!!不是说好了允许循环引用的吗?怎么肥四?怎么破???

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

代码语言:javascript
复制
线程名为main,开始调用业务fun方法
线程名为task-1,fun方法开始处理业务

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

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

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

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

代码语言:javascript
复制
mysql.password = 123456
redis.pwd = 654321

这时候访问端点/actuator/env,得到的结果是这样子的:

在这里插入图片描述
在这里插入图片描述

如图所示,感觉有点厚此薄彼有木有???其实一切事出有因,EnvironmentEndpoint使用Sanitizer进行脱敏处理,而它自带一些默认行为:

在这里插入图片描述
在这里插入图片描述

若不再这个范围内的key(比如上面的redis.pwd)也需要脱敏,很简单,价格配置项即可:

代码语言:javascript
复制
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即可。

代码语言:javascript
复制
// Since: 2.6.0
@FunctionalInterface
public interface SanitizingFunction {
	SanitizableData apply(SanitizableData data);
}

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

代码语言:javascript
复制
@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方式可以非常灵活方便的搞定:

代码语言:javascript
复制
@Bean
public 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,这也是本次版本升级的一大特征之一。代码上体现在这里:

代码语言:javascript
复制
// 2.5.7
public static class Pathmatch {
	private MatchingStrategy matchingStrategy = MatchingStrategy.ANT_PATH_MATCHER;
}

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

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

代码语言:javascript
复制
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领域可谓已有绝对的话语权,因此它开始引领:默认不允许循环引用。

本专栏上下文

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/12/13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 2.6版本主要新特性
      • 禁止循环引用
      • 更加灵活的自定义脱敏规则
      • ✌Spring MVC默认使用全新匹配策略
      • ✌Redis自动开启连接池
      • Spring Boot 2.4.x停止维护
      • 依赖升级
      • 删除和弃用
      • 官网新增SUPPORT标签页
  • 总结
    • 本专栏上下文
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档