HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

案例学习: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:362965
用户头像

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

关注

评论

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

【C语言】前言关键字

謓泽

11月月更

币安DAPP系统开发技术概念及篡改逻辑

I8O28578624

Wallys/Qualcomm IPQ5018 solution application wifi6 , support M.2 Card Slot for QCN9074 WIFI 6E Card

Cindy-wallys

802.11AX WIFI 6e ipq5018

“鸿蒙生态专家面对面”技术交流会,专家齐聚,等你前来!

HarmonyOS开发者

HarmonyOS

测试小白到月薪30K+的测试大佬学习路线图

千锋IT教育

应用实践:Paddle分类模型大集成者[PaddleHub、Finetune、prompt]

汀丶人工智能

nlp 文本分类 关系抽取 命名实体识别 11月月更

云小课|MRS基础原理之MapReduce介绍

华为云开发者联盟

大数据 华为云 企业号十月 PK 榜

计算机网络:电路、报文与分组交换

timerring

计算机网络 11月月更

升级到React-Router-v6

xiaofeng

React

PaddleBox:百度基于GPU的超大规模离散DNN模型训练解决方案

百度Geek说

企业号十月 PK 榜 PaddlePaddl 模型训练框架 大规模离散模型

没想到GoFrame的gcache天然支持缓存淘汰策略

王中阳Go

Go golang 高效工作 学习方法 11月月更

量化合约系统开发逻辑篡改方案

I8O28578624

Ten Million-Level Capacity Storage Solution of Student Management System - Examination

David

架构实战营

你要的react+ts最佳实践指南

xiaofeng

React

深度讲解React Props

夏天的味道123

React

RocketMQ Flink Catalog 设计与实践

晓双

flink Apache RocketMQ catalog

用git上传项目到GitHub或者码云全过程

肥晨

代码上传 githun 11月月更 Git上传

Backdrop Filter

肥晨

css3 css特效 11月月更 css滤镜

几个你必须知道的React错误实践

xiaofeng

React

CSS3渐变-快来感受CSS的伟大吧(差点闪瞎我的狗眼)

肥晨

11月月更 css3渐变 conic-gradient

案例解读华为隐私计算产品TICS如何实现城市跨部门数据隐私计算

华为云开发者联盟

云计算 华为云 隐私计算 企业号十月 PK 榜

11月必须要了解的一项福利

夏夜许游

AI 双十一 视觉智能

关于“React 和 Vue 该用哪个”我真的栓Q

京东科技开发者

Vue 前端 Vue 3 VUE 3.0 源码 react rout

Wallys//IPQ8072/IPQ8074/IPQ8072A/IPQ8074A/HighPower 802.11ax SoC for Routers, Gateways and Access Points

Cindy-wallys

802.11AX IPQ8072 IPQ8074 HighPower

基于BPMN2.0的业务流程引擎

GFE

前端 BPMN 流程引擎

公链defi质押挖矿分红dapp系统开发(合约定制)

开发微hkkf5566

珠宝加工厂:我的成本下降空间在哪里

华为云开发者联盟

云计算 物联网 华为云 企业号十月 PK 榜

京东云开发者|提高IT运维效率,深度解读京东云AIOps落地实践

京东科技开发者

人工智能 异常检测 时序架构 运维‘

探索行为可回溯系统的应用与实现

GFE

前端 监控

基于qiankun的微服务落地实践

GFE

微服务 前端 qiankun

深度理解Redux原理并实现一个redux

夏天的味道123

React

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