Spring Framework 5.3.0正式发布,在云原生路上继续发力

  • ✍前言
  • ✍正文
    • 新特性/功能升级
      • 通用升级
        • 1、ASM升级到9.0
        • 2、支持RxJava 3.0,过期对RxJava 1.0支持
        • 通过spring.spel.ignore属性可禁用SpEL模块
      • 核心容器
        • 对@Scheduled升级:CronExpression
        • CronExpression的使用
        • @Scheduled中Cron表达式增强
        • 其它增强
      • 数据访问与事务
        • 新增JdbcTransactionManager类
        • JdbcTemplate新增queryForStream()方法
        • @Transactional的timeout属性支持占位符取值
      • Web升级
        • CORS跨域配置类
        • 通过spring.xml.ignore属性可禁用xml解析模块
      • Spring MVC提升
        • 提升URL匹配效率
        • @ControllerAdvice可处理来自所有处理器的异常了
      • Spring WebFlux提升
      • Testing测试提升
  • ✍总结
          • ✔推荐阅读:
  • ♥关注A哥♥
在这里插入图片描述

✍前言

你好,我是YourBatman。

北京时间2020-10-27,Spring Framework 5.3.0版本正式发布。说明:Spring Framework 5.2.0.RELEASE的发布时间是2019-09哈。

注意:5.3.0版本号没有.RELEASE后缀哟,至于原因我前面写的这篇文章给了你完整解释哈:Spring改变版本号命名规则:此举对非英语国家很友好

在Spring Boot大行其道的今天,似乎Spring Framework的版本升级受到的关注越来越少了,殊不知对Spring Boot有多了解始于对Spring Framework的了解程度,因此保持对后者的一定关注会是很有意义的。

在这里插入图片描述

✍正文

Spring Framework 5.3.x是5.x版本的最后一个功能分支,此系列最低要求JDK8,且支持到了JDK15,以及还会扩展支持到明年的JDK17。

另外,对于刚刚发布的Spring Boot 2.4.0以及明年即将发布的Spring Boot 2.5.0均会基于此功能分支构建。官方建议:现在处在5.x / 4.x的用户均升级到Spring Framework 5.3.x版本上来,因为它将是一个面向未来的分支,并且提供长期维护(持续到2024年,持续时间可谓是最长的)。它是寿命同样长的4.3.x分支系列产品的后继产品,但此系列的寿命将在2020-12结束不再维护(毕竟真的很老了嘛)。

说明:Spring团队对于一般的功能分支,提供2-3年的维护,而对于4.3/5.3均提供了长达4年多的维护时间,这得益于它们是主版本号的最后一个功能分支

新特性/功能升级

诚如各位所知,Spring Framework是很多模块技术的总和,因此在新特性/功能增强方面我们分模块进行表述。

通用升级

1、ASM升级到9.0

ASM 9.0版本可是非常新的版本,如下图:

在这里插入图片描述
代码语言:javascript
复制
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.0</version>
</dependency>

Spring使用了最新版的ASM包,可谓紧跟潮流啊。

需要清楚的是Spring它并没有引入这个jar哦,而是fork了一份源代码放在spring-core里(报名改了,但内容基本没没变),这样做更加高效:

在这里插入图片描述

至于ASM 9.0的新功能增强,有兴趣可进入其官网阅读即可,直达电梯:https://asm.ow2.io

2、支持RxJava 3.0,过期对RxJava 1.0支持

这些都是通过适配器ReactiveAdapterRegistry实现的,它支持了:Reactor、RxJava 2/3、CompletableFuture、Java 9+的Flow.Publisher等等,并且以把对RxJava 1.0的支持标记为过期,预计下个功能分支会移除。

在这里插入图片描述
代码语言:javascript
复制
<dependency>
    <groupId>io.reactivex.rxjava3</groupId>
    <artifactId>rxjava</artifactId>
    <version>3.0.7</version>
</dependency>

可以看到RxJava 3.X也是非常非常新的,这点Spring做得不可为不好。

RxJava 3.X旨在用来替代RxJava 2,因此存在少量的二进制不兼容(简单说:不向下兼容,但改动也没那么的大),另外就是提供了基于Java 8 lambda友好的API,更加的普适了。

通过spring.spel.ignore属性可禁用SpEL模块

如果你的应用程序没使用/不依赖于SpEL,那么可以通过属性spring.spel.ignore来忽略掉它。SpEL是spring-expression是这个模块提供支持的,其实很多时候我们并不需要使用到SpEL表达式(特别是在现在的注解驱动使用中),那么我们就可以通过设置此属性值来禁用掉此模块功能,为系统减负(降低启动时间,节约内存)。

SpEL模块主要在两个地方被使用,均可通过此属性配置来关闭:

代码语言:javascript
复制
// 上下文了支持SpEL表达式,比如@Value里
AbstractApplicationContext:
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag(&#34;spring.spel.ignore&#34;);

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	...
	if (!shouldIgnoreSpel) {
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
	}
	...
}

// 支持@EventListener(condition = 'SpEL')
EventListenerMethodProcessor:

public EventListenerMethodProcessor() {
	if (shouldIgnoreSpel) {
		this.evaluator = null;
	} else {
		this.evaluator = new EventExpressionEvaluator();
	}
}</code></pre></div></div><p>只需要在类路径下的<code>spring.properties</code>文件添加属性<code>spring.spel.ignore = true</code>即可禁用掉SpEL模块的加载。</p><blockquote><p> 小贴士:类路径下的<code>spring.properties</code>文件是Spring Framework启动自动就会加载的,详可参见<code>org.springframework.core.SpringProperties</code>学习。

spring-expression模块的初始化动作不轻,Spring提供此属性我认为是在为云原生做不懈努力。但是,但是,但是,请你务必在确定没有使用SpEL的情况下才去关闭此开关,否则弄出线上bug背个锅就划不来了。当然喽,最好是能有个熟悉Spring的人hold住才去改它~

核心容器

此模块最大的升级就是对Cron表达式进行了提升。

对@Scheduled升级:CronExpression

在Spring场景下,Cron表达式只能通过@Scheduled注解去实现,而在5.3.0版本引入了一个新的API:CronExpression用于解析Cron表达式(编程式)。

CronExpression完全替代了CronSequenceGenerator(@Scheduled基于它实现)这个老的API,是因为后者基于java.util.Calendar实现的从而存在已知的一些问题,并且还没法修复(毕竟是JDK的锅嘛,Spirng团队也无能为力),因此新的CronExpression就使用了java.time解决了问题并且还提供了新的好用功能。

CronExpression的使用

在之前我们想要一个Cron表达式都是借助@Scheduled来完成,现在可以使用CronExpression轻松编程式来做喽:

代码语言:javascript
复制
public static void main(String[] args) {
    CronExpression expression = CronExpression.parse("10 * * * * *");
LocalDateTime start = LocalDateTime.now();
for (int i = 0; i &lt; 3; i++) {
    start = expression.next(start);
    System.out.println(start);
}

}

运行程序,控制台输出:

代码语言:javascript
复制
2020-11-15T11:28:10
2020-11-15T11:29:10
2020-11-15T11:30:10

该Cron表达式表示:每分钟的第10秒钟触发,因此结果是符合预期的。该parse方法入参是Cron表达式的字符串,该字符串带有六个以空格分隔的时间和日期字段,各部分表示为:

在这里插入图片描述

当然啦,还有些 * ?这样的通配符可以使用,具体的这里就不做过多介绍了。

@Scheduled中Cron表达式增强

我们经常会在@Scheduled使用Cron表达式,比如0 0 * * * *代表每个小时执行一次,对于这种常用的表达式这么写确实非常不直观,Spring 5.3.0新提出了宏的概念,让你可以用一个英文单词搞定,它由CronExpression提供支持:

代码语言:javascript
复制
CronExpression:
private static final String[] MACROS = new String[] {
		&#34;@yearly&#34;, &#34;0 0 0 1 1 *&#34;,
		&#34;@annually&#34;, &#34;0 0 0 1 1 *&#34;,
		&#34;@monthly&#34;, &#34;0 0 0 1 * *&#34;,
		&#34;@weekly&#34;, &#34;0 0 0 * * 0&#34;,
		&#34;@daily&#34;, &#34;0 0 0 * * *&#34;,
		&#34;@midnight&#34;, &#34;0 0 0 * * *&#34;,
		&#34;@hourly&#34;, &#34;0 0 * * * *&#34;
};</code></pre></div></div><p>如:<code>@Scheduled(cron = &#34;@hourly&#34;)</code>它就代表每小时执行一次,效果等同于<code>@Scheduled(cron = &#34;0 0 * * * *&#34;)</code>。其它的等同效果如上所示。</p><h5 id="f13fo" name="%E5%85%B6%E5%AE%83%E5%A2%9E%E5%BC%BA">其它增强</h5><p>Spring还对最后xxx天、工作日等提供了便捷操作,此处仅做出简单示例,详请还请参见官方文档哈。</p><div class="table-wrapper"><table><thead><tr><th style="text-align:left"><div><div class="table-header"><p>Cron表达式</p></div></div></th><th style="text-align:left"><div><div class="table-header"><p>含义</p></div></div></th></tr></thead><tbody><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 L * *</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月的最后一天午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 L-3 * *</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月的第三天到最后一天午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 * * 5L</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月的最后一个星期五午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 * * THUL</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月最后一个星期四的午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 1W * *</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每月第一个工作日的午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 LW * *</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月最后一个工作日的午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 ? * 5#2</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每个月的第二个星期五午夜</p></div></div></td></tr><tr><td style="text-align:left"><div><div class="table-cell"><p>0 0 0 ? * MON#1</p></div></div></td><td style="text-align:left"><div><div class="table-cell"><p>每月的第一个星期一午夜</p></div></div></td></tr></tbody></table></div><h4 id="3bdl1" name="%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E4%B8%8E%E4%BA%8B%E5%8A%A1">数据访问与事务</h4><h5 id="9euqt" name="%E6%96%B0%E5%A2%9EJdbcTransactionManager%E7%B1%BB">新增JdbcTransactionManager类</h5><p>它继承自<code>DataSourceTransactionManager</code>,它主要是对<code>doCommit、doRollback</code>时产生的异常借助<code>SQLExceptionTranslator</code>实现转换,简单的说可以自定义这个转换器来实现异常转换喽,这在5.3.0之前是写死的:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">5.3.0之前DataSourceTransactionManager:
@Override
protected void doCommit(DefaultTransactionStatus status) {
	...
	try {
		con.commit();
	} catch (SQLException ex) {
		throw new TransactionSystemException(&#34;Could not commit JDBC transaction&#34;, ex);
	}
}

5.3.0之后DataSourceTransactionManager:
@Override
protected void doCommit(DefaultTransactionStatus status) {
...
try {
con.commit();
} catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}

// @since 5.3  默认实现是同之前版本使用的TransactionSystemException包装
// 但子类JdbcTransactionManager对此方法实现了覆盖
protected RuntimeException translateException(String task, SQLException ex) {
	return new TransactionSystemException(task + &#34; failed&#34;, ex);
}

5.3.0新增的JdbcTransactionManager类:
@Override
protected RuntimeException translateException(String task, SQLException ex) {
DataAccessException dae = getExceptionTranslator().translate(task, null, ex);
if (dae != null) {
return dae;
}
return super.translateException(task, ex);
}

JdbcTemplate新增queryForStream()方法

这是5.3.0新增方法,这样就可以允许在可关闭的对象上进行惰性迭代,提高查询效率(不过JdbcTemplate貌似用得很少,至少国内是这样)。

@Transactional的timeout属性支持占位符取值

在这之前,@Transactional注解timeout属性的值只能写死,现在可以写成${...}从环境中动态取值了,方便许多且更富弹性。

Web升级

CORS跨域配置类

CorsConfiguration跨域配置类新增属性allowedOriginPatterns,用于通过通配符模式声明动态范围的域,之前只有allowedOrigins只能写死。

通过spring.xml.ignore属性可禁用xml解析模块

在Spring Boot大行其道的今天,面向元数据/注解编程成为了主流,基于xml配置的Spring应用越来越少。

为了加快启动速度和减少内存开销,Spring提供了spring.xml.ignore这个属性来禁用掉xml解析模块,如果你没有用到xml功能的话。同样的,我觉得这是Spring为云原生做的又一努力。

代码语言:javascript
复制
// 默认可以将.properties和xml配置加载到Properties类
PropertiesLoaderUtils:

private static final boolean shouldIgnoreXml = SpringProperties.getFlag(&#34;spring.xml.ignore&#34;);

public static void fillProperties(Properties props, Resource resource) throws IOException {
	...
	if (filename != null &amp;&amp; filename.endsWith(XML_FILE_EXTENSION)) {
		if (shouldIgnoreXml) {
			throw new UnsupportedOperationException(&#34;XML support disabled&#34;);
		}
		props.loadFromXML(is);
	}
	...
}

ConfigurationClassBeanDefinitionReader:
ResourcePropertiesPersister:

如果禁用了,不仅仅不会去加载xml文件,而且连相关的converters和codecs都不会去加载了。禁用方式为在类路径下的spring.properties文件里添加属性spring.xml.ignore = true即可。

Spring MVC提升

提升URL匹配效率

众所周知:Spring MVC中可以写Ant分隔的URL,也就是我们常用的@PathVariable,使用起来非常方便。但是,基于Ant风格的URL存在性能问题,特别实在高并发情况下尤为明显,不可忽视

基于Ant风格的URL使用的AntPathMatcher进行匹配,Spring自己也知道它在高并发下存在着性能问题,在今年的早些时候官方也写了篇博客描述这个现象:URL Matching with PathPattern in Spring MVC

在5.3.0版本后,基于Ant风格的URL的匹配效率有了非常大的提升,具体原理和使用方式限于篇幅,将在后面文章用专文详解。

@ControllerAdvice可处理来自所有处理器的异常了

在过去@ControllerAdvice只能处理来自@Controller类型控制器的异常,但是我们知道Spirng MVC至少支持4种控制器类型,如HttpRequestHandler就是其中一种。从5.3.0版本开始就都可以用@ControllerAdvice来做拦截统一处理啦~

Spring WebFlux提升

  • 提供一个新类DefaultPartHttpMessageReader提供一个完全反应式的消息阅读器,它将缓冲区流转换为Flux<Part>
  • 提供一个新类PartHttpMessageWriter用于写Flux<Part>
  • 为Apache Http Components提供一个新的WebClient连接器

Testing测试提升

  • Spring的测试框架现在已基于JUnit Jupiter 5.7、JUnit 4.13.1、TestNG 7.3.0来构建(竟然还没放弃JUnit4.x,推荐信项目使用JUnit Jupiter 5.x哈,好用多了)
  • RestTemplate的客户端REST测试支持Multipart data

✍总结

总的来说,Spirng Framework这次的升级着力点主要在启动速度、性能使用上。作为5.x分支的最后一个功能分支,可圈可点,个人的升级建议是可以升也建议升。

当然,现在一般不会单独升级Spring Framework的版本,而是随着Spring Boot一起升级。截止稿前,Spirng Boot 2.4.0(基于Spring Framework 5.3.x)已正式发布,将会在下篇文章开启介绍,欢迎关注。