本文主要研究下迁移到java9的一些注意事项。html
一、代码不模块化,先迁移到jdk9上,好利用jdk9的api
二、代码同时也模块化迁移java
好比sun.security.x509,在java9中归到java.base模块中,可是该模块没有export该package能够经过运行的时候添加--add-exports java.base/sun.security.x509=ALL-UNNAMED来修改exports设定spring
好比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
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
)shell
split packages
)由于模块中能够exports指定包给其余模块,若是多个模块exports一样的包名会形成混乱,特别如有其余类库同时requires这两个模块,就不知道该引用那个模块的了。api
若是一个模块的接口参数或返回类型使用了其余模块的类,则建议requires transitive它依赖的模块oracle
在设计模块的时候,要尽量考虑到是否会有循环依赖的问题,若是有则须要从新设计app
services特别适合用来解耦调用方与实现类依赖的问题,若是接口有多种实现类,调用方没必要要requires全部的实现类,只须要requires接口便可,使用services类型来加载实现类的实例。经过在module-path去动态添加实现模块实现解耦。maven
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。
好比javax.xml.bind.JAXBException,JAXB已经纳入到java.xml.bind模块,在java命名后面添加
--add-modules java.xml.bind
若是图省事,把$JAVA_HOME及全部第三方类库添加到module-path,而后来个
--add-modules ALL-MODULE-PATH
反射缘由引发,因为旧系统没有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上;而后再模块化。