java9遷移注意事項

go4it發表於2018-02-26

本文主要研究下遷移到java9的一些注意事項。

遷移種類

1、程式碼不模組化,先遷移到jdk9上,好利用jdk9的api 2、程式碼同時也模組化遷移

幾點注意事項

不可讀類

比如sun.security.x509,在java9中歸到java.base模組中,但是該模組沒有export該package

可以通過執行的時候新增--add-exports java.base/sun.security.x509=ALL-UNNAMED來修改exports設定

內部類

比如sun.misc.Unsafe,原本只想讓oracle jdk team來使用,不過由於這些類應用太廣泛了,為了向後相容,java9做了妥協,只是將這些類歸到了jdk.unsupported模組,並沒有限定其可讀性。

➜  ~ java -d jdk.unsupported
jdk.unsupported@9
exports com.sun.nio.file
exports sun.misc
exports sun.reflect
requires java.base mandated
opens sun.misc
opens sun.reflect
複製程式碼

刪除的類

java9刪除了sun.misc.BASE64Encoder,這種情況只能改用其他api,比如java.util.Base64

classpath vs module-path

java9引入了模組系統,同時自身的jdk也模組化了,引入了module-path,來遮蔽classpath,也就是說在java9優先使用module-path,畢竟jdk本身都模組化了,應用本身沒有模組化的話,java9通過unnamed modules及automatic modules機制來隱式模組化,當然classpath在java9上還能繼續使用,比如配合module-path使用等。

沒有模組化的jar在classpath會被歸到unnamed modules;在module-path則會被自動建立為automatic modules(一個automatic modules會宣告transitive依賴所有named和unnamed module,然後匯出自身的package)

一個包名不能在多個模組中出現(split packages)

因為模組中可以exports指定包給其他模組,如果多個模組exports同樣的包名會造成混亂,特別若有其他類庫同時requires這兩個模組,就不知道該引用那個模組的了。

傳遞依賴

如果一個模組的介面引數或返回型別使用了其他模組的類,則建議requires transitive它依賴的模組

小心迴圈依賴

在設計模組的時候,要儘可能考慮到是否會有迴圈依賴的問題,如果有則需要重新設計

使用services來實現optional依賴

services特別適合用來解耦呼叫方與實現類依賴的問題,如果介面有多種實現類,呼叫方不必要requires所有的實現類,只需要requires介面即可,使用services型別來載入實現類的例項。通過在module-path去動態新增實現模組實現解耦。

模組版本管理

module-info.java不支援宣告版本號,但是建立jar包的時候,可以通過--module-version設定。不過模組系統查詢模組的時候還是使用模組名來查詢(如果module-path裡頭有多個重名模組,則模組系統知會使用找到的第一個,自動忽略後續的同名模組),版本依賴問題不在模組系統解決範疇內,交由maven之類的依賴管理工具去管理。

模組資源訪問

模組化之後資原始檔也收到保護,只能由該模組去訪問本模組自身的資原始檔,如果需要跨模組訪問,也必須藉助ModuleLayer找到目標模組,再呼叫目標模組去載入該模組的資原始檔。

反射的使用

這裡涉及到deep reflection問題,所謂的deep reflection就是通過反射去呼叫一個class的非public元素。module-info.java的exports宣告package只是允許該package直接所屬的類允許訪問其public元素,並不允許反射呼叫非public元素。

反射在模組系統裡頭需要特殊宣告才允許使用(使用opens宣告允許deep reflection),這樣就導致很多使用反射的類庫諸如spring,需要額外配置才能遷移到java9。解決方案有兩個:一個是opens package包名給需要反射的模組,比如spring.beans等;一個就是直接opens整個模組。

預設--illegal-access=permit,同時該設定只適用於java9之前的package在java9被不允許訪問,不適用於java9中新的不允許訪問的package.(建議遷移到模組化系統時設定為deny)

不過就是在模組系統中包名不一樣就屬於不同的包,沒有繼承關係,比如com.service.func1與com.service.func2這兩個是不同的包,你不能只opens com.service,必須分別指定這樣就導致需要open的的package比較多。因此open整個module可能更省事一點,但也屬於比較粗暴的做法。

上面的做法是在原來module-info.java裡頭去做修改,另外一種是在執行java或javac的時候通過指定的命令來修改原來的關係。比如

java ... --add-opens source-module/source-package=target-module
複製程式碼

如果需要匯出給unnamed modules,則target-module為ALL-UNNAMED

當然如果是新的系統,那就不建議使用反射了,可以使用MethodHandles及VarHandles。

常見問題和措施

ClassNotFoundException/NoClassDefFoundError

比如javax.xml.bind.JAXBException,JAXB已經歸入到java.xml.bind模組,在java命名後面新增

--add-modules java.xml.bind
複製程式碼

如果圖省事,把$JAVA_HOME及所有第三方類庫新增到module-path,然後來個

--add-modules ALL-MODULE-PATH
複製程式碼

illegal reflective access by xxx to method java.lang.ClassLoader.defineClass

反射原因引起,由於舊系統沒有module-info,因此在java命名新增引數加以修改

--add-opens java.base/java.lang=ALL-UNNAMED
複製程式碼

確定依賴的模組

通過IDE或者jdeps分析

jdeps --class-path 'classes/lib/*' -recursive -summary app.jar
複製程式碼

jdeps只是靜態程式碼分析,如果有使用反射用的類jdeps分析不出來,需要自己手工requires,如果dependency是optional的,可以requires static

對模組單元測試的可讀性問題

如果單元測試時單獨模組的話,可以在執行時通過--add-exports或--add-opens來授予單元測試模組對目標模組的可讀性及反射能力。另外由於split packages問題,單元測試類的包名不能跟目標模組包名重複。原來maven工程那種test

小結

可以分兩步走遷移到java9,首先是先不模組化,只先跑在jdk9上;然後再模組化。

doc

相關文章