java9迁移注意事项

本文主要研究下迁移到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

  • MethodHandles.Lookup
  • method-handles-in-a-nutshell
  • Java 9 Modularity
  • Java 9 揭秘(9. 打破模块封装)

原文发布于微信公众号 - 码匠的流水账(geek_luandun)

原文发表时间:2018-02-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA后端开发

vue自动保存表单功能的实现

最近想实现一个表单内容的自动保存,原来是想通过监听表单的change事件来解决.但后面想想,现在都是数据驱动了,监听数据变化就行了. 页面表单如下:

73640
来自专栏FreeBuf

打造一款属于自己的远程控制软件(二)

前一篇文章介绍了软件的整体架构,接下来对被控端进行详细讲解,主要介绍被控端各个功能模块的关键技术以及开发过程中遇到的坑,希望对各位读者有借鉴作用。 被控端工作流...

31150
来自专栏资深Tester

一定要知道的,那些Linux操作命令(二)

22860
来自专栏Android-薛之涛

Android-多线程

        通俗的说:我们平日里打开的QQ,微信,简书,都是一个进程。进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程...

10620
来自专栏北京马哥教育

rsync - Linux下进行文件同步命令

rsync是Linux下进行文件同步到一个命令,可以同步两台计算机到文件与目录,利用查找文件中到不同块以减少数据传输。也可以在一台电脑到不同目录间同步,比如可以...

57160
来自专栏python成长之路

面向对象封装的web服务器

20850
来自专栏蓝天

Tcpdump 的用法

更新时间:2005-12-26 11:55 阅读提示:第一种是关于类型的关键字,主要包括host,net,port, 例如 host 210.27.48.2,指...

12040
来自专栏小樱的经验随笔

【Python Learning第一篇】Linux命令学习及Vim命令的使用

学了两天,终于把基本命令学完了,掌握以后可以当半个程序员了♪(^∇^*) 此文是一篇备忘录或者查询笔记,如果哪位大佬看上了并且非常嫌弃的话,还请大佬不吝赐教,多...

394100
来自专栏LuckQI

Java多线程总结三

8520
来自专栏飞雪无情的博客

Go语言实战笔记(十五)| Go 并发示例-Runner

这篇通过一个例子,演示使用通道来监控程序的执行时间,生命周期,甚至终止程序等。我们这个程序叫runner,我们可以称之为执行者,它可以在后台执行任何任务,而且我...

8830

扫码关注云+社区

领取腾讯云代金券