java 迁移数据
重要要点
- 以模块化方式实现应用程序鼓励良好的设计实践,例如关注点和封装的分离。
- Java平台模块系统(JPMS)使开发人员可以定义应用程序的模块是什么,其他模块如何使用它们以及它们依赖于哪些其他模块。
- 可以将JPMS模块定义添加到已经使用其他系统定义应用程序模块的应用程序中,例如Maven模块或Gradle子项目。
- JDK附带的工具可帮助开发人员将现有代码迁移到JPMS。
- 应用程序代码仍然可以依赖Java-9之前的库,这些jar文件被视为特殊的“自动”模块。 这使逐步迁移到Java 9变得更加容易。
本文演示了一个案例研究,该案例研究了为了使用新的Java平台模块系统(JPMS),实际应用程序需要进行的更改。 请注意,您不必这样做是为了使用Java 9,但模块系统(通常被称为拼图)的理解无疑,随着时间的推移,成为Java开发人员的一项重要技能。
我将逐步介绍重构Java 8应用程序所使用的步骤,该Java 8应用程序已经以模块化方式进行了组织,以使用新的Java模块系统。
下载Java 9
首先下载并安装最新版本的JDK 9 。 当前,这是一个早期访问版本(本文使用9-ea + 176)。 在了解Java 9对系统的影响之前,您可能不希望它成为默认的Java版本。 您可能希望创建一个新的环境变量$JAVA9_HOME
而不是更新$ JAVA_HOME使其指向新安装。 在整篇文章中,我都会使用这种方法。
还有许多其他教程,讨论了使用Java 9可能需要采取的一些步骤。我们将讨论局限于模块化组件,但您可能想查看Oracle的《 迁移指南》以获取更多信息。
模块化
在Java 9上下文中,您最常听到的功能是Project Jigsaw ,它是Java模块的介绍。 关于这到底是什么或它是如何工作的,有很多教程和文章,本文将介绍如何迁移现有代码以使用新的Java Platform Module System。
许多开发人员惊讶地发现,使用Java 9不必在自己的代码中添加模块化。内部API的封装可能是考虑Java 9时开发人员所关心的功能之一,但这仅仅是因为这部分的Jigsaw可能影响开发人员并不意味着开发人员需要充分拥抱模块化才能利用Java 9。
如果您确实希望利用Java平台模块系统 (JPMS),可以使用一些工具来帮助您,例如jdeps
依赖关系分析器,Java编译器和您自己的IDE。
我不会谈论如何将您的特定应用程序分解为模块,如果一开始就没有做到这一点可能很难实现(例如, 将JDK组织成模块所花费的很多年!) ,我将假设您的应用程序已经结构化为较小的块,可能使用Maven模块或Gradle子项目 ,或者可能使用IDE中的子项目或模块。
您会发现许多教程,例如《 Jigsaw 快速入门指南》 ,其中假定了一个类似于图1的项目结构。
![](https://i-blog.csdnimg.cn/blog_migrate/933288e16a4cc3d3748e2581db2aadd7.png)
图1
该结构具有一个src
目录和一个test
目录,然后所有模块都是这些文件夹的子文件夹。 这与Maven或Gradle熟悉的结构有所不同,在Maven或Gradle中,每个模块都包含自己的src
和test
目录。 值得庆幸的是,您不必以这种方式重新安排整个应用程序(伴随着麻烦的是让您的构建工具来了解此更新的结构)。 只要您了解许多教程中结构上的差异,就可以继续使用Maven / Gradle布局并采用JPMS。 关键是要知道哪些目录是应用程序的源根-在Maven / Gradle布局中,它是src
和test
文件夹。
![](https://i-blog.csdnimg.cn/blog_migrate/9484aefeaf6f8435796fbbe502864748.png)
图2
您需要做的第一件事是将module-info.java
文件放入模块的源根目录中,定义模块的名称。 您可以手动创建它,或者某些IDE可以为您创建它。 图3显示了用于我的服务模块的简单模块module-info.java
:
![](https://i-blog.csdnimg.cn/blog_migrate/0f113ebcd109f4428cf61ab8419aa3a2.png)
图3
请注意,这是如何保存在src
目录中以及作为目录结构根目录的文件夹旁边的。
现在,在您的IDE中编译项目,或者在命令行上导航到此src目录,并使用类似以下内容的模块进行编译:
> "%JAVA9_HOME%"\bin\javac -d ..\mods\service module-info.java com\mechanitis\demo\sense\service\*.java com\mechanitis\demo\sense\service\config\*.java
此时,您将看到很多编译错误(请参见图4 )。
![](https://i-blog.csdnimg.cn/blog_migrate/abe4f04fdaf2ca5f14650e53f8d791ce.png)
图4
有27个错误,这可能令人惊讶-这个项目正在完美地编译并正在运行,我们所做的只是添加了一个module-info.java
文件,现在不再编译。 原因是我们现在必须更加明确地说明我们希望使用自己的模块的模块。 这些必需的模块可以是:JDK中的模块; 我们自己创建的其他模块; 或来自外部依存关系的模块,此时可能将是自动模块 。
在这里, jdeps
依赖关系分析器可以帮助我们识别需要在module-info.java
声明的模块。 为了成功运行此程序,您需要执行以下操作:
- 您的(JPMS之前)模块代码的jar文件,或包含(JPMS之前)类文件的目录。 请注意,如果您只是在最后一步中尝试编译源文件,那么此时您将没有任何工作类文件。 您可能需要删除module-info.java并重新编译。
- 模块代码的类路径。 如果您习惯在IDE内运行,尤其是在使用Maven或Gradle管理依赖项时,这很难定位或创建。 在IntelliJ IDEA中,您可以通过在运行应用程序或测试时查看运行窗口内的详细信息来确定要使用的有效类路径。 在图5中 ,我只需要滚动到右侧,然后将其余的行复制为蓝色即可。
![](https://i-blog.csdnimg.cn/blog_migrate/4fffa614b6c1190e1011900d82db95d1.png)
图5
现在我们有了运行jdeps
所需要的jdeps
,我们将使用带有相应标志的Java 9版本:
> "%JAVA9_HOME%"\bin\jdeps --class-path %SERVICE_MODULE_CLASSPATH% out\production\com.mechanitis.demo.sense.service
最后一个参数是包含我的服务模块的类文件的目录。 运行此命令时,将得到图6的输出。
split package: javax.annotation [jrt:/java.xml.ws.annotation, C:\.m2\...\javax.annotation-api-1.2.jar]
com.mechanitis.sense.service -> java.base
com.mechanitis.sense.service -> java.logging
com.mechanitis.sense.service -> C:\.m2\...\javax-websocket-server-impl-9.4.6.jar
com.mechanitis.sense.service -> C:\.m2\...\javax.websocket-api-1.0.jar
com.mechanitis.sense.service -> C:\.m2\...\jetty-server-9.4.6.jar
com.mechanitis.sense.service -> C:\.m2\...\jetty-servlet-9.4.6.jar
com.mechanitis.sense.service -> com.mechanitis.sense.service.config com.mechanitis.sense.service
com.mechanitis.sense.service -> java.io java.base
com.mechanitis.sense.service -> java.lang java.base
com.mechanitis.sense.service -> java.lang.invoke java.base
com.mechanitis.sense.service -> java.net java.base
com.mechanitis.sense.service -> java.nio.file java.base
com.mechanitis.sense.service -> java.util java.base
com.mechanitis.sense.service -> java.util.concurrent java.base
com.mechanitis.sense.service -> java.util.concurrent.atomic java.base
com.mechanitis.sense.service -> java.util.function java.base
com.mechanitis.sense.service -> java.util.logging java.logging
com.mechanitis.sense.service -> java.util.stream java.base
com.mechanitis.sense.service -> javax.websocket javax.websocket-api-1.0.jar
com.mechanitis.sense.service -> javax.websocket.server javax.websocket-api-1.0.jar
com.mechanitis.sense.service -> org.eclipse.jetty.server jetty-server-9.4.6.jar
com.mechanitis.sense.service -> org.eclipse.jetty.servlet jetty-servlet-9.4.6.jar
com.mechanitis.sense.service -> org.eclipse.jetty.websocket.server javax-websocket-server-impl-9.4.6.jar
com.mechanitis.sense.service -> org.eclipse.jetty.websocket.server.deploy javax-websocket-server-impl-9.4.6.jar
com.mechanitis.sense.service.config -> java.lang java.base
com.mechanitis.sense.service.config -> java.lang.invoke java.base
com.mechanitis.sense.service.config -> javax.websocket javax.websocket-api-1.0.jar
com.mechanitis.sense.service.config -> javax.websocket.server javax.websocket-api-1.0.jar
图6
它警告我有关拆分包的信息 ,这是因为相同的包名称出现在两个不同的模块或jar文件中-在这种情况下,这是因为相同的包同时出现在java.xml.ws.annotation(来自JDK)和javax中.annotation-api.jar。 然后它告诉我服务模块使用的所有软件包,以及这些软件包位于哪个模块/ jar文件中。我可以使用此信息来创建适用于我的模块的module-info.java
文件:
module com.mechanitis.demo.sense.service {
requires java.logging;
requires javax.websocket.api;
requires jetty.server;
requires jetty.servlet;
requires javax.websocket.server.impl;
}
java.base
包含大多数基本的JDK内容,但是我不需要包含它,因为它是默认包含的。 我确实需要弄清楚我的外部依赖项的自动模块名称是什么。
自动模块
Java 9和JPMS旨在考虑以下事实:大多数代码,尤其是我们大家都在使用的通用库,很可能不会使用JPMS(即,不会完全模块化,而module-info.java定义了依赖性和权限) )。 为了弥合差距并简化迁移,模块化代码仍然可以具有不是真正模块的jar依赖项。 而是将它们视为自动模块。 特殊模块,当您将jar文件放在类路径上时,仍然可以以与原始方式完全相同的方式访问jar中的所有软件包。 作为使用自动模块的开发人员,对我们来说唯一棘手的事情是弄清楚它的名字是什么。 默认情况下,名称或多或少是jar文件名,减去任何版本号。 因此jetty-server-9.4.1.v20170120.jar
产生一个名为jetty.server
的自动模块(请注意使用点而不是连字符)。
注意:Java 9的最新版本允许库开发人员使用 JAR文件清单属性'Automatic-Module-Name' 指定其自动模块名称 ,因此您可能需要检查jar文件本身以找出哪些内容。它的模块名称是。
使用我们的新模块
现在,我的代码可以正确编译。 现在,我们可以开始将另一个模块迁移到JPMS模块的工作。 让我们为用户模块创建一个空的module-info.java
, 图7显示了新的编译器错误。
![](https://i-blog.csdnimg.cn/blog_migrate/4fac5f83a3b001d449e88f7dd25dbcb7.png)
图7
这很容易修复,我们只需要从服务模块中更新module-info.java
文件,以允许其他模块通过导出来访问服务包:
module com.mechanitis.demo.sense.service {
requires java.logging;
requires javax.websocket.api;
requires jetty.server;
requires jetty.servlet;
requires javax.websocket.server.impl;
exports com.mechanitis.demo.sense.service;
}
现在重新编译会产生另一个错误:
Error:(3, 33) java: package com.mechanitis.demo.sense.service is not visible
(package com.mechanitis.demo.sense.service is declared in module com.mechanitis.demo.sense.service, but module com.mechanitis.demo.sense.user does not read it)
这意味着我们需要更新用户模块以需要服务模块:
module com.mechanitis.demo.sense.user {
requires com.mechanitis.demo.sense.service;
}
完成后,所有内容都会编译,我们可以成功运行我们的用户服务。
结论
我们经历了重构现有应用程序以使用Java平台模块系统的过程。 尽管将应用程序拆分为模块有很多好处,但是将现有的应用程序迁移到JMPS并不是一件容易的事,只有在好处显而易见的情况下,才应尝试进行此操作。
java 迁移数据