效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

張哥說技術發表於2023-12-21

來源:京東雲開發者



一、前言

眾所周知,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,你敢嘗一嘗嗎

以上的壓測結果對於我們來說非常誘人,ZGC配合JDK17的效能對於其他JDK和垃圾收集器組合來說是碾壓獲勝,且不論任何機器配置下,都推薦使用ZGC,ZGC的停頓時間達到亞毫秒級,吞吐量也比較高。這個對於一個高併發業務場景下,對於資源的最佳化是非常客觀的。

3.3 OpenJDK17下載地址

提供了一個下載地址:



四、行雲部署上的實踐方案

4.1 Spring Boot 2.7

4.1.1 pom.xml版本依賴

實踐的版本選擇上我選擇了2.7大版本下的最新小版本,即Spring Boot 2.7.17版本。pom.xml依賴如下:



































<?xml version="1.0" encoding="UTF-8"?><project xmlns="         xmlns:xsi="         xsi:schemaLocation="     <modelVersion>4.0.0</modelVersion>
   <groupId>com.jd.magnus</groupId>    <artifactId>magnus-multi-ddd</artifactId>    <version>1.0.0-SNAPSHOT</version>    <packaging>pom</packaging>
   <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.7.17</version>    </parent>
   <modules>        <module>magnus-multi-ddd-adapter</module>        <module>magnus-multi-ddd-application</module>        <module>magnus-multi-ddd-domain</module>        <module>magnus-multi-ddd-infrastructure</module>        <module>magnus-multi-ddd-client</module>        <module>magnus-multi-ddd-worker</module>    </modules>
   <properties>        <maven.compiler.source>17</maven.compiler.source>        <maven.compiler.target>17</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <jsf-lite.version>1.0.0-HOTFIX-T2</jsf-lite.version>        <ump.version>20221231.1</ump.version>    </properties></project>

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版本說明:

4.1.3 單元測試升級

在Spring Boot 2.7版本,已經不再依賴JUnit4, 而是將Test換成了 JUnit Jupiter, 這也導致之前單元測試使用的方法和註解會產生變化。

常用的一些方法和註解變化如下:

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

4.1.4 hibernate-validator包依賴問題

Springboot從2.3以後,spring-boot-starter-web中不再引入hibernate-validator,需要手動引入。此處可以直接引用spring-boot-starter-validation的包,裡面會間接引用hibernate-validator的包,且版本號可以被spring boot parent統一管理。

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-validation</artifactId></dependency>

4.1.5 診斷升級相容性方法

如果是老專案版本升級,Spring Boot 提供了一種在啟動時分析應用程式環境並列印診斷資訊的方法,而且還可以在執行時為您臨時遷移屬性。要啟用該功能,請將以下依賴項新增到您的專案中:

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-properties-migrator</artifactId>    <scope>runtime</scope></dependency>

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.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.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 不相容問題**/@Beanpublic 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 <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {                List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());                mappings.clear();                mappings.addAll(copy);            }
           @SuppressWarnings("unchecked")            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {                try {                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");                    field.setAccessible(true);                    return (List<RequestMappingInfoHandlerMapping>) 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,你敢嘗一嘗嗎

  • 其次, 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,您可以新增以下依賴項來解決該問題:<dependency>    <groupId>jakarta.xml.bind</groupId>    <artifactId>jakarta.xml.bind-api</artifactId>    <version>3.0.1</version> <!-- 根據您的需求選擇合適的版本 --></dependency>
  • 此處正好多說一下Spring boot 3.0的一個小問題,@Resource在Spring boot 3.0上,已經不再依賴javax.annoation包,所以包路徑也由javax.annotation.Resource改為了jakarta.annotation.Resource。當然此處在2.7版本依然相容,可以不用修改。

以下貼一個報名改動對比圖:

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

垃圾回收器的話,從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,你敢嘗一嘗嗎

  • 大家有兩種選擇:a. 直接安裝Idea JDK17外掛。b. 或者自己下載OpenJDK17安裝,然後繫結路徑。

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

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

  • 意:對外的JSF client JDK還要用JDK8進行打包,也不要在client JDK中使用JDK17的新語法特性。因為外部依賴系統不一定是JDK17版本的。

  • 本地Debug注意事項:

當本地debug時,需要配置debug的啟動引數,配置VM options,並將上面【行雲部署上的實踐方案-c.相容性問題說明】章節中所說的相容性問題配置新增到該處。如下圖所示:

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

此方法也適用於本地debug JUnit Test單元測試本地除錯時用。當然,如果為了避免每個單元測試都要手動配置,可以點選圖中的Edit configuration templates,配置模板,做到一勞永逸。具體配置如下圖所示:

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎

  • Maven test注意事項:

同理,在使用maven命令對工程進行test命令時,也需要額外配置啟動引數,來相容JDK17訂單安全性檢查,需要在pom檔案中的maven-surefire-plugin增加如下配置:

效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎



七、總結

目前,將部門內的京旗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-


來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3000845/,如需轉載,請註明出處,否則將追究法律責任。

相關文章