[译]Java 9:一步步迁移项目到Jigsaw(模块化)

时间:2022-06-01 18:56:33

Java 9出来了。 我们来试试一个简单的Spring项目。 为了使练习更具挑战性,我们还要尝试使用新的模块系统。 该项目只是一个使用Spring,JDBC和Shedlock的简单示例。

1、阅读所有可用的文档和规格说明。 嗯,听起来很无聊。 跳过第一步。

2、下载JDK并尝试运行该项目。 我们很幸运,我们所有的依赖只使用公共Java API,所以它运行有效。 在现实的项目中,你可能不会那么幸运。 一些库使用Java 9中不可用的已移除的内部API。你必须等待库作者来修复它们,这通常是不容易的。 幸运的是,这不是我们的情况。

我们的项目运行在Java 9 JVM上,我们可以在这里停下来。 我们可以利用紧凑的字符串和其他优化,而不再进一步。 但是我们也想使用新的语言功能。

3、切换编译器到Java 9:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>1.9</source>
        <target>1.9</target>
    </configuration>
</plugin>

仍然有效,很酷。 我们可以在这里使用新的“--release”选项,但不幸的是,IntelliJ还不支持。 再次,我们可以在这里停下来。 现在我们可以使用所有新的语言功能。 但是让我们试用Jigsaw。

4、添加module-info.java,再次运行项目

module shedlock.example {
}

该项目没有编译。 我们收到很多“package XYZ is not visible”的编译错误。 这是正常的 - 我们正在使用外部库,而且我们还没有在module-info中声明这些依赖。 幸运的是,我们有最新的IntelliJ,它会帮我们修复。 这是一些手工工作,我期望它将来能够一键就完成,但现在已经足够好了。 或者,你可以使用jdeps工具

module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
}

请注意,没有库有module-info声明。 我们在这里看到的是从JAR名称自动生成名称的模块。 库作者在发布模块化版本时,某些名称可能会更改。 例如,slf4j.api将很可能被重命名为org.slf4j。 虽然类似这样在项目里不是什么问题。 当我们升级slf4j时,我们也可以改变依赖关系。

但在库文件里,这是不同的。

一旦我们声明依赖,如果没有调整所有的项目就很难改变它,这取决于我们。 这就是为什么Maven警告我们“检测到所需的基于文件名的automodules。请不要将此项目发布到公共仓库!” 现在我们已经声明了所有的依赖项,并且编译了项目! 我们完成了! 对?

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to module spring.core
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
at spring.core@4.3.7.RELEASE/org.springframework.cglib.core.ReflectUtils$1.run(ReflectUtils.java:54)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at spring.core@4.3.7.RELEASE/org.springframework.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:44)
... 25 more

没那么快。

Java模块在编译期和运行时被强制执行。我已在IntelliJ执行了这个项目,在IntellJ看到module-info.java以及自动使用了模块路径而非类路径,也依次开启了运行时检查。我们得到'module java.base does not "opens java.lang" to module spring.core'。Spring正尝试反射来自模块java.base的类,而模块系统不允许这样做。Spring 5支持Java 9,那么我们更新到最新的Spring 5RC4,但没有用。

我们再次降一下级。

我们可以查阅文档吗?没办法,去*找解答。 我们必须打开java.base模块。 由于我们无法修改,唯一的方法是使用命令行参数。

使用--add-opens java.base/java.lang=spring.core命令行参数。现在spring.core模块可以通过反射访问到了java.base。我们来执行它并转到下一个例外。

Caused by: java.lang.IllegalAccessException: class org.springframework.cglib.proxy.Enhancer (in module spring.core) cannot access class net.javacrumbs.shedlockexample.SpringConfig$EnhancerBySpringCGLIB$81275b04 (in module shedlock.example) because module shedlock.example does not export net.javacrumbs.shedlockexample to module spring.core
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:589)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
at java.base/java.lang.reflect.Field.set(Field.java:778)

Spring正尝试访问我们项目的类,我们得到这个错误'module shedlock.example does not export net.javacrumbs.shedlockexample to module spring.core',我们来做JVM告诉我们的并导出我们唯一的包。

exports net.javacrumbs.shedlockexample to spring.core;

仍然不幸:我们得到'module shedlock.example does not "opens net.javacrumbs.shedlockexample" to module spring.core'。我们导出了编译时的包,但不是用于反射。 到目前为止,模块系统的错误消息一直很好,但是这里有点混乱。我们的代码编译了,但在运行时模块系统却抱怨说我们没有导出包。导出应该只影响编译期,那么我们为什么在运行时得到这错误呢?

原因很简单:Spring使用CGLIB生成我们类的子类,所以编译时和运行时之间的区别有点模糊。 根据规范,“一个普通的模块只能对明确导出或显式公开(或两者)的包进行反射访问。”在运行时生成的类是反射访问吗? 我们试图通过公开来取代导出。 它仍然有效。稍后有一些异常,我们最终得到了一个有效模块声明。

module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
    opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context;
}

酷,但还不完美。 想象一下,你项目中包含更多的包,并且你希望在大多数应用程序中使用Spring,Jackson或其他使用反射的库。 一个接一个地公开它们听起来好像很多工作。 幸运的是,我们可以公开整个模块。

6、使用关键词open公开模块

open module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
}

再次引用规范。“一个公开模块,使用open修饰,在编译时只允许访问输入那些明确导出的包,但在运行时运行访问输入它所有的包,就好像所有的包都导出一样。”这就是 我们需要的。

我们完成了! 这是一个有趣的练习,我们学到了什么呢? 首先,可以将你的项目迁移到模块。 这值得么? 很可能不会。 我建议等到工具和库准备就绪。 在流行的边缘很有趣,但对于一个生产项目,我会给它几个月,直到所有的工具和库都是Java 9准备好,所有的严重错误都是固定的。 还有一点可以弄清楚Jigsaw如何适应整个生态系统。

示例项目里可以看到所有步骤。