效能加速包:SpringBoot 2.7&JDK 17,你敢嘗一嘗嗎
來源:京東雲開發者
支援了JDK 17的新特性,例如switch表示式、文字塊、區域性變數型別推斷等。這使得在Spring應用程式中使用JDK 17的特性變得更加容易和方便。 利用了JDK 17的效能最佳化:JDK 17引入了許多效能最佳化,例如新的垃圾收集器、執行緒排程等。Spring 2.7利用了這些效能最佳化,可以提高Spring應用程式的效能和響應速度。 預設配置與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上執行。 改進的安全性:JDK 17透過增強加密演算法、禁用舊版TLS和SSL協議等增強了安全性。Spring Boot 2.7利用了這些安全改進,提高了應用程式的安全性。 持續的效能最佳化和提升。相比於老系統的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的動力主要源於兩個方面:一是更為安全的語言特性,二是更加優異的垃圾回收器和效能提升。
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的效能提升效果。
以上的壓測結果對於我們來說非常誘人,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, 這也導致之前單元測試使用的方法和註解會產生變化。
常用的一些方法和註解變化如下:
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版本比較好。
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-UNNAMED4.3.6 Swagger相容性配置
分兩步:
properties配置檔案中增加以下配置:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
程式碼在配置類中新增BeanPostProcessor重寫:
/**
* 增加如下配置可解決Spring Boot 2.7.15 與Swagger 3.0.0 不相容問題
**/
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
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);
}
"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下的一些常用工具類(專案中儘量不要再用):
其次, 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版本依然相容,可以不用修改。
以下貼一個報名改動對比圖:
垃圾回收器的話,從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
在Idea中,需要安裝JDK17,然後專案需要配置對應的JDK17版本,截圖如下:
大家有兩種選擇:a. 直接安裝Idea JDK17外掛。b. 或者自己下載OpenJDK17安裝,然後繫結路徑。
modules也需要更改Language level, 截圖如下:
注意:對外的JSF client JDK還要用JDK8進行打包,也不要在client JDK中使用JDK17的新語法特性。因為外部依賴系統不一定是JDK17版本的。
本地Debug注意事項:
當本地debug時,需要配置debug的啟動引數,配置VM options,並將上面【行雲部署上的實踐方案-c.相容性問題說明】章節中所說的相容性問題配置新增到該處。如下圖所示:
此方法也適用於本地debug JUnit Test單元測試本地除錯時用。當然,如果為了避免每個單元測試都要手動配置,可以點選圖中的Edit configuration templates,配置模板,做到一勞永逸。具體配置如下圖所示:
Maven test注意事項:
同理,在使用maven命令對工程進行test命令時,也需要額外配置啟動引數,來相容JDK17訂單安全性檢查,需要在pom檔案中的maven-surefire-plugin增加如下配置:
目前,將部門內的京旗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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- webgl 效能優化初嘗Web優化
- 嘗試
- 嘗試用go寫一個音樂搜尋的包Go
- 初嘗 DockerDocker
- JAVAFX嘗試Java
- 嘗試1
- Flutter + Rust 高效能的跨端嘗試FlutterRust跨端
- 為什麼你應該嘗試 “全棧”全棧
- 為什麼你應該嘗試“全棧”全棧
- 初嘗試swiftSwift
- ios NFC嘗試iOS
- 【譯】下一個你值得認真嘗試的框架 —— Sapper框架APP
- 嘗試說一說事件的使用事件
- 用VR技術學車 你敢嗎?VR
- [Vuex系列] - 初嘗Vuex第一個例子Vue
- 嘗試手寫一個註解框架框架
- 嘗試 LeetcodeLeetCode
- 嘗試手寫promisePromise
- 初次嘗試HTTPSHTTP
- Redis crackit 漏洞嘗試Redis
- 嘗試用python開發一款圖片壓縮工具1:嘗試 pillow庫Python
- 看了此文,你還敢說你懂了Javascript運算子嗎JavaScript
- grpc 的第一次嘗試RPC
- 設計模式(一) 動態代理初嘗試設計模式
- webpack4搭建的一次嘗試Web
- 記b站的一次react嘗試React
- 嘗試使用Knative建立一個應用
- 第一次嘗試鴻蒙開發鴻蒙
- 壓縮 json 的一些嘗試JSON
- 第一次嘗試編寫javaJava
- 一次恢復oracle的嘗試(轉)Oracle
- svg 線條動畫淺嘗SVG動畫
- 嘗試 Leetcode(二)LeetCode
- 微信小程式 TypeScript 嘗試微信小程式TypeScript
- oracle函式初次嘗試Oracle函式
- 淺嘗UIAppearance的使用UIAPP
- Ruby/Gtk2初嘗GTK2
- mview on prebuilt table用法嘗試ViewUI