要点

  • 通过模块化的方式开发应用程序,实现更好的设计,如关注点分离和封装性。
  • 通过Java平台模块化系统(JPMS),开发者可以定义他们的应用程序模块,决定其他模块如何调用他们的模块,以及他们的模块如何依赖其他模块。
  • 对于已经使用了其他模块系统(如Maven或Gradle)的应用程序来说,还是有可能再加入JPMS。
  • JDK为开发者提供了一些工具,用于将现有的代码迁移到JPMS。
  • 应用程序代码仍然可以依赖Java 9之前的类库,这些类库的jar包被看成是一种特别的“自动化”模块,从而简化了向Java 9迁移的工作。

这篇文章提供了一个学习案例,演示一个真实的应用程序需要做出哪些变更才能迁移到JPMS。当然,要使用Java 9不一定要做这些事情,不过对于Java开发者来说,了解模块化系统(通过被叫作Jigsaw)无疑是一个非常重要的技能。

我将会演示如何一步步地使用新的Java模块化系统来重构一个基于Java 8的应用程序。

下载Java 9

首先要下载和安装最新版本的JDK 9,目前只有抢先预览版(这里使用的是9-ea+176版本)。在全面了解Java 9之前,你可能不希望把它作为系统的默认Java版本。所以,你可以不改变原先的$JAVA_HOME环境变量,而是创建一个新的变量$JAVA9_HOME,并把它指向新安装的JDK目录。我将在这篇文章里使用这个新变量。

关于使用Java 9要做的其他一些步骤,可以参看其他现成的教程。我们主要还是讨论模块化组件,不过你也可以参考Oracle的迁移指南。

模块化

关于Java 9,你会经常听到人们谈论Jigsaw项目,也就是Java的新模块化系统。关于Jigsaw已经有很多相关教程,而这篇文章将介绍如何使用JPMS对已有代码进行迁移。

要使用Java 9,并不一定要在代码里添加模块化,这一点让很多开发者都感到惊讶。开发者在使用Java 9时最为关注的一点或许是内部API的封装性,虽然这个会影响到开发者,但这并不意味着要使用Java 9就一定要完全拥抱模块化。

要利用好JPMS,有很多工具可以帮到你,比如jdeps依赖分析器、Java编译器和你所使用的IDE。

我不会在这里讲解如何将应用程序拆解成模块,如果一开始没有做好模块化规划,后续就会变得很困难(JDK的模块化拆解就花了好几年的时间)。相反,我假设你的应用程序已经是按照小型的部件组织在一起的,它们可能是Maven模块或者Gradle子项目,又或者是IDE里的子项目或模块。

你会发现,在很多教程里,包括Jigsaw的入门指南,都会假设一个如下所示的项目结构。

 
图1

项目里有一个单独的src目录和一个单独的test目录,其他所有模块都是这两个目录的子目录。这个与Maven或Gradle的结构不太一样,它们的每个模块都有自己的src目录和test目录。不过好在你不一定要重新组织整个应用程序的代码结构(也不需要让你的构建工具重新去理解这种结构),你可以继续使用Maven或Gradle的结构,只要你知道在不同的教程里可能使用了不同的结构。关键是你要知道应用程序的根目录是哪一个——在Maven或Gradle里,根目录就是src目录和test目录。

 
图2

你要做的第一件事情是在模块的根目录放置一个module-info.java文件,用来定义模块的名字。你可以手动创建这个文件,也可以让IDE帮你创建这个文件。图3展示了我的模块的module-info.java文件:


图3

现在在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
  • 1
  • 2

这个时候你会发现有很多编译错误(如图4所示)。


图4

总共有27个错误,或许你会感到很惊讶,之前这个项目完全可以编译并正常运行,但在添加了一个module-info.java文件之后就无法编译了。问题在于,我们现在要显式地指定我们的模块所依赖的其他模块。这些模块包括JDK的模块、我们自己创建的其他模块,或者来自外部依赖的模块(在这里我们需要自动模块)。

jdeps依赖项分析器可以帮助我们确定需要在module-info.java里声明哪些模块。为了让程序运行起来,你需要一些东西:

一个包含模块代码的jar包,或者一个包含class文件的目录。要注意,在上一步编译之后根本得不到class文件,你需要先移除module-info.java文件后重新编译才能得到需要的class文件。 
模块代码的类路径。如果你习惯了在IDE里运行程序,并使用了Maven或Gradle来管理依赖,可能就很难找到或设置类路径。在IntelliJ IDEA里,你可以在运行窗口中看到类路径。如图5所示,我把滚动条滚动到适当的位置,然后把蓝色字体的内容拷贝出来。


图5

现在我们可以运行jdeps,并使用Java 9的一些标记:

> "%JAVA9_HOME%"\bin\jdeps --class-path %SERVICE_MODULE_CLASSPATH% out\production\com.mechanitis.demo.sense.service
  • 1

最后一个参数是包含了class文件的目录。在运行这个命令的时候,我们会得到如下的输出。

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

这些是分裂包(split pacakge)的警告信息,说明相同的包名出现在两个不同的模块或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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

java.base包含了几乎所有的JDK基本类库,不过我不需要显示地声明它,因为它是默认包含的。我甚至不需要知道外部依赖项的自动模块名称。

自动模块

Java 9和JPMS考虑到大部分的代码在一开始并不会使用JPMS(例如并没有通过module-info.java来定义依赖和权限从而实现完全的模块化)。为了解决这个问题并简化迁移工作,你的模块化代码仍然可以使用jar包依赖(这些jar包不是真正的模块)。这些jar包被称为自动模块,它们内部的包名可以被自由访问,只要它们处在类路径里,你就可以像以前那样随意访问它们。对于开发者来说,我们唯一要做的就是找出它们的名字。默认情况下,它们的名字一般就是去掉了版本号的jar包文件名。例如,jetty-server-9.4.1.v20170120.jar对应的自动模块名就是jett.server(使用点号代替了破折号)。

注意:最新版本的Java 9允许开发者通过jar包的manifest属性“Automatic-Module-Name”指定自动模块的名字,所以你可以通过检查jar包来找出模块名。

使用我们的新模块

现在我的代码可以通过编译,接下来让我们来迁移另一个模块。先创建一个空的module-info.java文件,图6显示了新的编译错误。


图6

要修复这些错误非常简单,只要更新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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

重新编译后生成另一个错误:

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)
这说明要为user模块声明service模块: module com.mechanitis.demo.sense.user {
requires com.mechanitis.demo.sense.service;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后就可以正常编译模块,并成功运行user服务。

结论

我们演示了如何重构一个已有的应用程序,让它用上JPMS。将应用程序拆分成模块是有好处的,将应用程序迁移到JMPS也是一件很有意义的事情,不过在这样做之前要先确定这样确实能给你带来好处。

from: https://blog.csdn.net/rickiyeat/article/details/78071385

最新文章

  1. xpath学习积累
  2. java中Scanner和random的用法
  3. oracle常用数据类型
  4. linux内核数据包转发流程(三)网卡帧接收分析
  5. Excel和notepad++加之更换
  6. 虚拟机安装Ubuntu14.04打开FireFox提示Server not found
  7. 打包发布Python模块或程序,安装包
  8. 两款不错的Linux密码生成工具
  9. python正则表达式写[强口令检测]
  10. proxysql 系列 ~ 高可用架构
  11. Quartz.net 2.4.1 使用记录
  12. UVA524 素数环 Prime Ring Problem
  13. python工具的选择
  14. js之选项卡(tag标签)
  15. iOS push新的调用方法
  16. DataSet & DataTable &DataRow 深入浅出
  17. 第3章 如何用DAP仿真器下载程序
  18. SQL数据库数据类型详解
  19. Linux中几个与文档相关的命令
  20. MessageFormat格式化数字

热门文章

  1. tornado样板
  2. 根据节点解析xml
  3. LCA离线算法Tarjan的模板
  4. Edit Distance——经典的动态规划问题
  5. maven中profile的激活方式
  6. SGU 261. Discrete Roots
  7. (翻译)Xamarin.Essentials 最新预览版的更多跨平台 API
  8. Java数组(int为例)
  9. python脚本实现ipv6的ddns功能
  10. 基于wsimport生成代码的客户端