NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

案例学习:Jigsaw 模块化迁移

  • 2017-08-07
  • 本文字数:4632 字

    阅读完需:约 15 分钟

要点

  • 通过模块化的方式开发应用程序,实现更好的设计,如关注点分离和封装性。
  • 通过 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

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

(点击放大图像)

图4

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

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

  1. 一个包含模块代码的 jar 包,或者一个包含 class 文件的目录。要注意,在上一步编译之后根本得不到 class 文件,你需要先移除 module-info.java 文件后重新编译才能得到需要的 class 文件。
  2. 模块代码的类路径。如果你习惯了在 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

最后一个参数是包含了 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

这些是分裂包(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;
}

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;
}

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

复制代码
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;
}

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

结论

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

关于作者

Trisha Gee是一个 Java Champion,并为多个行业开发过 Java 应用程序,包括金融业、制造业、软件行业和非盈利组织。她擅长高性能 Java 系统,热衷于提升开发者效率,并涉足开源项目开发。她是 JetBrains 的 Java 开发人员倡导者,这让她一直处于 Java 技术的最前沿。

查看英文原文: Painlessly Migrating to Java Jigsaw Modules - a Case Study

2017-08-07 17:362737
用户头像

发布了 322 篇内容, 共 134.4 次阅读, 收获喜欢 144 次。

关注

评论

发布
暂无评论
发现更多内容

Mac版本After Effects 2023怎么切换中英文UI界面

展初云

教程 Mac软件 After Effects

一文了解常见的Python爬虫框架Scrapy

TiAmo

python 爬虫 Scrapy

close()关闭文件方法

智趣匠

PDF 批量处理软件 BatchOutput PDF激活最新版

mac大玩家j

PDF Mac软件 pdf处理工具

HarmonyOS CPU与I/O密集型任务开发指导

HarmonyOS开发者

HarmonyOS

英伟达,不能只做孤独的胜利者

自象限

蓝易云:Linux 命令 - cat 和 tail

百度搜索:蓝易云

云计算 Linux 运维 cat tail

英文版Premiere Pro怎么切换成中文?「Mac篇」

展初云

教程分享 Mac软件 Premiere Pro

华为云API Explorer重磅推出API编排,开发者0代码高效构建工作流(体验用户招募中)

华为云PaaS服务小智

云计算 软件开发 华为云

视频剪辑中生成花字特效的代码案例详述

阿里云视频云

云计算 阿里云 视频云

轻量应用服务器,助力个人开发者最低成本创业

YG科技

破解 VMware Fusion Pro 13 VM虚拟机 ,VMware Mac永久激活安装教程

晴雯哥

工业自动化:国内外常用工业组态软件

2D3D前端可视化开发

物联网 工业自动化 工业控制系统 工业组态软件 组态工具

华秋供应链,让硬科技创业更简单

华秋电子

创业

蓝易云:linux搭建sftp服务器教程!

百度搜索:蓝易云

云计算 Linux 运维 云服务器 sftp

别学提示词:42类法大师 - 万字精华浓缩

豆哥

ChatGPT 提示词

支持向量机(SVM)

小魏写代码

数字中国下西洋,需要一支怎样的船队?

白洞计划

AI 网易 风控

华为云耀云服务器L实例:带你探索轻量应用服务器的魅力

YG科技

“00后”整顿职场,终于轮到网安圈了

脑极体

数据湖

IoTLink版本更新V1.43.0 新增部分ERP功能:销售合同 库存管理

山东云则信息科技

物联网

Java开发分析软件JProfiler Macv14.0.0永久激活版 附注册

晴雯哥

大道总是孤独的——查理芒格如是说

少油少糖八分饱

投资 长期主义 能力圈 查理芒格 股东大会

苹果电脑专业图表绘制推荐OmniGraffle Pro 正式版激活中文

胖墩儿不胖y

Mac软件 绘图软件 绘图工具

为什么自动化测试要分层

老张

自动化测试

私藏管理秘诀帮你打造高绩效团队

敏捷开发

团队管理 项目管理 Scrum 敏捷开发 高绩效团队

人工智能核心基础 - 规划和概要

茶桁

人工智能 机器学习 深度学习 CNN RNN

案例学习:Jigsaw模块化迁移_Java_Trisha Gee_InfoQ精选文章