性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗

‍‍

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第1张图片

一、前言

众所周知,SpringBoot3.0迎来了全面支持JDK17的局面,且最低支持版本就是JDK17,这就意味着,Spring社区将完全抛弃JDK8,全面转战JDK17。作为JAVA开源生态里的扛把子,Spring可以说是整个JAVA生态的风向标,可以说,当Spring转战JDK17,会很快带领JAVA生态全面地跟进JDK17。而我本篇文章重点讲述Spring版本和JDK17升级中的实践整理。

二、为什么是Spring Boot 2.7

Spring Boot 3.0是全面放弃JDK8,而Spring社区当然不会把事情做的那么决绝,在推出3.0之前,Spring就开始着手布局JDK17升级。而2.7版本,就是Spring社区为了升级JDK17而推出的过渡版本,具体包括以下几个方面的升级和改进:

  1. 支持了JDK 17的新特性,例如switch表达式、文本块、局部变量类型推断等。这使得在Spring应用程序中使用JDK 17的特性变得更加容易和方便。

  2. 利用了JDK 17的性能优化:JDK 17引入了许多性能优化,例如新的垃圾收集器、线程调度等。Spring 2.7利用了这些性能优化,可以提高Spring应用程序的性能和响应速度。

  3. 默认配置与JDK 17兼容:Spring Boot 2.7的默认配置与JDK 17兼容,这意味着您不需要进行额外的配置就可以在JDK 17上运行Spring Boot应用程序。这点很重要,Spring Boot 2.7依赖于Servlet 4.0,而Servlet 4.0本身并不直接支持JDK 17, Spring Boot 2.7为了支持JDK 17进行了一些兼容性调整和优化,以使其能够在JDK 17上运行。

  4. 改进的安全性:JDK 17通过增强加密算法、禁用旧版TLS和SSL协议等增强了安全性。Spring Boot 2.7利用了这些安全改进,提高了应用程序的安全性。

  5. 持续的性能优化和提升。相比于老系统的2.1到2.3版本,2.7版本对内存管理和bean管理都有很大程度的优化和提升,内存使用更加合理。虽然官网没有给出所谓的性能提升对比,但性能的优化和系统的稳定性是一定加强的。

总之,使用Spring Boot 2.7可以更好地利用JDK 17的特性,提高应用程序的性能和响应速度,同时还可以获得更好的兼容性和安全性。所以通过Spring Boot 2.7过渡升级JDK17,是一种更为温和方式,且遇到的兼容性问题最小。当然这不全是我自顾自说,Spring官方给出了这样的说明:

If you’re currently running with an earlier version of Spring Boot, we strongly recommend that you upgrade to Spring Boot 2.7 before migrating to Spring Boot 3.0.

三、为什么是JDK17

关于JDK17的新特性和优势,对于它支持的新语法和编程特性,我在此不再赘述,因为网上有很多文章介绍。

对于我落地JDK17的动力主要源于两个方面:一是更为安全的语言特性,二是更加优异的垃圾回收器和性能提升。

3.1 安全的语言特性

  • 安全性首先体现在JDK17对于包扫描和反射的权限控制,可能大家对当年的FastJson漏洞记忆犹新,它的病根在于对反射的滥用。而对于这种反射的滥用,在JDK17里有了更严格的控制。

JDK 17对反射进行了优化,主要表现在对反射调用进行了权限控制。具体来说,它通过setAccessible()方法启动或禁止访问安全检查开关。当参数值为true时,反射的对象在使用时取消安全检查,提高反射的效率;当参数值为false时,反射的对象执行安全检查。这样的优化使得在处理反射调用时,可以更加灵活地控制访问权限。

  • 除此之外,JDK 17增强了包扫描的权限控制。在之前的版本中,Java的包扫描是基于类的,而在JDK 17中,它扩展到了对整个包的权限控制。这使得开发者可以更加精细地控制对特定包的访问权限。

  • 针对于语法本身,引入了密封的类和接口,具体使用细节大家可以网上查看。通过密封类和接口,进一步增加了面向对象开发的封闭性,提升代码质量的安全可靠。

3.2 垃圾收集器

JDK17引入了ZGC作为垃圾收集器,此处引用一下曲振富老师做的关于不同垃圾收集器在不同JDK版本下的压测结果:

压测服务背景:

DOS平台上选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用JDK8、JDK11和JDK17进行部署和压测。整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512Kb的数据。最终产出不同GC回收器的各项指标数据,来分析GC的性能提升效果。

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第2张图片

以上的压测结果对于我们来说非常诱人,ZGC配合JDK17的性能对于其他JDK和垃圾收集器组合来说是碾压获胜,且不论任何机器配置下,都推荐使用ZGC,ZGC的停顿时间达到亚毫秒级,吞吐量也比较高。这个对于一个高并发业务场景下,对于资源的优化是非常客观的。

3.3 OpenJDK17下载地址

提供了一个下载地址:https://adoptium.net/zh-cn/temurin/releases/?version=17&os=linux&arch=x64

四、行云部署上的实践方案

4.1 Spring Boot 2.7

4.1.1 pom.xml版本依赖

实践的版本选择上我选择了2.7大版本下的最新小版本,即Spring Boot 2.7.17版本。pom.xml依赖如下:

 
   


    4.0.0


    com.jd.magnus
    magnus-multi-ddd
    1.0.0-SNAPSHOT
    pom


    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.17
    


    
        magnus-multi-ddd-adapter
        magnus-multi-ddd-application
        magnus-multi-ddd-domain
        magnus-multi-ddd-infrastructure
        magnus-multi-ddd-client
        magnus-multi-ddd-worker
    


    
        17
        17
        UTF-8
        1.0.0-HOTFIX-T2
        20221231.1
    

4.1.2 动态配置

Spring Boot 2.7对动态配置进行了更新。具体来说,Spring Boot 2.7更改了自动配置注册文件的路径和格式,从META-INF/spring.factories变更为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。

同时,Spring Boot 2.7还引入了新的注解@SpringBootApplication,该注解包含了@EnableAutoConfiguration和@ComponentScan等注解,使得配置更加简洁和方便。此外,Spring Boot 2.7还更新了一些自动配置的类和方法,以支持新版本的Spring Framework和Java。

  • 废弃的方法和类删除。列一下主要删除的方法和类:

 
   
SpringBootServletInitializer:在Spring Boot 2.7中,该类已经被移除,建议使用SpringBootServletWebServerApplicationContext来代替。
ServletWebServerFactoryCustomizer:这个接口已经从Spring Boot 2.7中移除,可以使用WebServerFactoryCustomizer来代替。
BasicErrorController:这个类已经从Spring Boot 2.7中移除,可以使用ErrorController接口来代替。
ContentNegotiationStrategy:这个接口已经从Spring Boot 2.7中移除,可以使用RequestMappingHandlerMapping的setContentTypeResolver(ContentTypeResolver)方法来代替。
HttpMessageConverters:这个接口已经从Spring Boot 2.7中移除,可以使用HttpMessageConvertingComparator来代替。
HttpMessageConvertingComparator:这个类已经从Spring Boot 2.7中移除,可以使用ComparatorChain来代替。
ServletWebServerFactoryCustomizerBeanPostProcessor:这个类已经从Spring Boot 2.7中移除。
SpringBootApplicationContextLoader:这个类已经从Spring Boot 2.7中移除,可以使用SpringApplicationWebApplicationContext来代替。
SpringBootServletInitializerAutoConfiguration:这个类已经从Spring Boot 2.7中移除,可以使用SpringBootServletWebServerApplicationContextAutoConfiguration来代替。

此外,还有一些被移除的配置属性,例如spring.http.converters.preferred-json-mapper、spring.jackson.serialization-features.default-pretty-print-xml、spring.jackson.serialization-features.sort-property-names-by-default等。其他信息大家可以看Spring的版本更新说明:

github上的release版本说明:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes 

4.1.3 单元测试升级

在Spring Boot 2.7版本,已经不再依赖JUnit4, 而是将Test换成了 JUnit Jupiter, 这也导致之前单元测试使用的方法和注解会产生变化。

常用的一些方法和注解变化如下:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第3张图片

4.1.4 hibernate-validator包依赖问题

Springboot从2.3以后,spring-boot-starter-web中不再引入hibernate-validator,需要手动引入。此处可以直接引用spring-boot-starter-validation的包,里面会间接引用hibernate-validator的包,且版本号可以被spring boot parent统一管理。

 
   

    org.springframework.boot
    spring-boot-starter-validation

4.1.5 诊断升级兼容性方法

如果是老项目版本升级,Spring Boot 提供了一种在启动时分析应用程序环境并打印诊断信息的方法,而且还可以在运行时为您临时迁移属性。要启用该功能,请将以下依赖项添加到您的项目中:

 
   

    org.springframework.boot
    spring-boot-properties-migrator
    runtime

4.2 行云部署配置

4.2.1 镜像配置

首先需要新的基础镜像包,包括OpenJDK17的。之前行云的镜像市场上是没有相关的镜像的,后来联系科技运维同事帮忙制作了新的镜像,新的镜像包是基于Tomcat应用类型的,大家可以在jdos的中国站的镜像市场搜索到。镜像名如下,:

 
   
base_tomcat/java-jd-centos7-jdk17-tomcat8.5.42-ngx197:latest

4.2.2 编译配置

编译配置中,需要选择的JDK版本为17,同时Maven的版本也可以尽量选高一些。因为按照惯例,maven的版本会对JDK的版本兼容性有所不同,一般越是高版本的Maven对JDK17兼容性更好。虽然官方没有明确说明Maven版本支持情况,但我们选择高版本的Maven是比较稳妥的选择,所以在JDOS上我们选择maven-3.9.0版本比较好。

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第4张图片

4.2.3 JVM参数配置

然后就是配置JVM启动参数,我们需要开启ZGC.具体启动参数以4C8G的资源为例,配置参数如下:

 
   
-Xms5324m  -Xmx5324m  -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m  -XX:MaxDirectMemorySize=983m  
 -Djava.library.path=/usr/local/lib -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/Logs 
 -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 
 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300  
 -XX:+UseZGC

此处需要注意,ZGC不要配置并行GC线程的数量,并发标记线程数等信息,配置了反而会出现启动报错情况。

4.3 兼容性问题说明

关于兼容性问题曲振富老师在他的文章里很详细得介绍了,包括如何兼容京东的UMP, DUCC等。这些中间件的兼容性问题产生主要由于JDK17中对于反射和扫描的安全性检查导致的,一个简单的解决办法是将没开放的module强制对外开放。所以需要一些额外配置。

先整理结论,额外配置集合如下,该集合可以配置在VM 启动参数之中:

 
   
--add-opens java.base/sun.security.action=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/sun.util.calendar=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED
--add-opens java.base/java.security=ALL-UNNAMED
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/sun.nio.ch=ALL-UNNAMED
--add-opens java.management/java.lang.management=ALL-UNNAMED
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED
--add-opens java.management/sun.management=ALL-UNNAMED
--add-opens java.base/sun.security.action=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

4.3.1 SGM依赖需要加入

 
   
--add-opens java.management/java.lang.management=ALL-UNNAMED 
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED 
--add-opens java.management/sun.management=ALL-UNNAMED

4.3.2 R2M需要加入

 
   
--add-opens java.base/java.time=ALL-UNNAMED

4.3.3 DUCC依赖需要加入

 
   
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED
--add-opens java.base/java.security=ALL-UNNAMED
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
--add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED 
--add-opens java.base/java.net=ALL-UNNAMED 
--add-opens java.base/sun.nio.ch=ALL-UNNAMED

4.3.4 AKS依赖需要加入

 
   
--add-exports java.base/sun.security.action=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/sun.util.calendar=ALL-UNNAMED

4.3.5 Pfinder依赖需要加入

 
   
--add-opens java.base/sun.net.util=ALL-UNNAMED

4.3.6 Swagger兼容性配置

分两步:

  • properties配置文件中增加以下配置:

 
   
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
  • 代码在配置类中新增BeanPostProcessor重写:

 
   
/**
 * 增加如下配置可解决Spring Boot 2.7.15 与Swagger 3.0.0 不兼容问题
**/
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {


            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }


            private  void customizeSpringfoxHandlerMappings(List mappings) {
                List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }


            @SuppressWarnings("unchecked")
            private List getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }

4.3.7 JDK维度兼容性问题(只挑我遇到的问题重点说)

  • JDK11就删除了javaFX库,所以该库下的所有方法在JDK17中不可用。如果你是从JDK8直接升级到JDK17, 需要注意,javaFX下的javafx.util包方法有可能会被大家不小心用到。

以下列举一下javafx.util下的一些常用工具类(项目中尽量不要再用):

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第5张图片

  • 其次, Java EE(Java Enterprise Edition)规范在 Java 9 之后被重新命名为 Jakarta EE。这是由于 Java EE 规范的开源版本迁移到了 Eclipse Foundation,并改名为 Jakarta EE。

因此有一些包名路径变更,为了兼容JSF,需要手动引入一些JAR包。但由于我们部署环境采用的是外置的Tomcat8,所以还是包含java EE的相关包。不需要额外加入,但本地debug时,需要加入。

尽管 Jakarta EE 是 Java EE 的继任者,但为了保持向后兼容性,许多 Java EE 规范和 API 在 Jakarta EE 中仍然存在,并且在 Jakarta EE 中的命名空间从 javax 变为 jakarta。且一些命名空间也改变了,比如javax包下的方法和属性都不能再试用,例如: javax.xml.bind.*更改为jakarta.xml.bind.*。以下有一个该问题引起的JSF报错修复:

 
   
关于JSF启动有报错信息:运行时找不到 javax.xml.bind.JAXBException 类。在 JDK 9 及更高版本中,javax.xml.bind 包被移除了,并且不再包含在标准的 Java SE 中。
如果您的项目依赖于 JAXB API,您可以尝试以下解决方法之一:
如果您使用的是 JDK 8 或更早版本,请确保您的项目使用的是兼容的 JDK 版本。
如果您使用的是 JDK 9 或更高版本,并且需要使用 JAXB API,您可以添加以下依赖项来解决该问题:

    jakarta.xml.bind
    jakarta.xml.bind-api
    3.0.1 
  • 此处正好多说一下Spring boot 3.0的一个小问题,@Resource在Spring boot 3.0上,已经不再依赖javax.annoation包,所以包路径也由javax.annotation.Resource改为了jakarta.annotation.Resource。当然此处在2.7版本依然兼容,可以不用修改。

以下贴一个报名改动对比图:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第6张图片

垃圾回收器的话,从JDK14开始,已经删除了CMS,所以在JDK17下,只建议使用ZGC。

还有一个最大的变化是之前的--illegal-access参数不在可用,如果在java 17使用这个参数访问受限的api则会报出InaccessibleObjectException,大多数情况下只要升级了依赖项是不会碰到这个情况的,但如果出现问题,则可以使用--add-opens来对不可访问的api授权。以上的SGM,R2M,DUCC,AKS,Pfinder的兼容性问题都是因为这个特性变化引起的。

五、脚手架支持

目前最全的DDD脚手架已经支持Spring Boot 2.7.17 和JDK17 ,下载脚本如下:

 
   
mvn archetype:generate \
            -DarchetypeGroupId=com.jd.magnus \
            -DarchetypeArtifactId=magnus-multi-ddd-archetype \
            -DarchetypeVersion=1.0.0-SNAPSHOT \
            -DinteractiveMode=false \
            -DarchetypeCatalog=remote \
            -Dversion=1.0.0-SNAPSHOT \
            -DgroupId=com.jdl.sps \
            -DartifactId=bff-demo1

六、IDE配置注意事项

  • 在Idea中,需要安装JDK17,然后项目需要配置对应的JDK17版本,截图如下:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第7张图片

  • 大家有两种选择:a. 直接安装Idea JDK17插件。b. 或者自己下载OpenJDK17安装,然后绑定路径。

modules也需要更改Language level, 截图如下:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第8张图片

  • 注意:对外的JSF client JDK还要用JDK8进行打包,也不要在client JDK中使用JDK17的新语法特性。因为外部依赖系统不一定是JDK17版本的。

  • 本地Debug注意事项:

当本地debug时,需要配置debug的启动参数,配置VM options,并将上面【行云部署上的实践方案-c.兼容性问题说明】章节中所说的兼容性问题配置添加到该处。如下图所示:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第9张图片

此方法也适用于本地debug JUnit Test单元测试本地调试时用。当然,如果为了避免每个单元测试都要手动配置,可以点击图中的Edit configuration templates,配置模板,做到一劳永逸。具体配置如下图所示:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第10张图片

  • Maven test注意事项:

同理,在使用maven命令对工程进行test命令时,也需要额外配置启动参数,来兼容JDK17订单安全性检查,需要在pom文件中的maven-surefire-plugin增加如下配置:

性能加速包:SpringBoot 2.7&JDK 17,你敢尝一尝吗_第11张图片

七、总结

目前,将部门内的京旗API服务, 发货平台BFF服务,物流发货商家基础信息服务作为试点,已经在行云测试环境上用JDK17+Spring Boot2.7版本进行试运行,京东三方依赖包括JSF lite版本,ducc, easyJob,jmq, 云redis, ump, pfinder。经测试,兼容性没有太大问题,服务可用。后续还会进一步观察和测试。

据我了解,现在开源社区里,以apache为代表的大型开源项目都对JDK17有了不错的兼容, 未来可以逐步再从Spring Boot 2.7升级到Spring Boot 3.0。‍

-end-

你可能感兴趣的:(java,spring,boot,后端,spring,开发语言)