写点什么

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

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

关注

评论

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

架构师训练营第八章-作业1

A Matt

<<前端进阶篇>> PDF 出炉了 — 「阿宝哥」,精心准备的 6 万多字 170 页的前端进阶资料

阿宝哥

大前端

英特尔®AI计算盒参考设计发布 加速智能边缘崛起

最新动态

计算机网络基础(十一)---网络层-OSPF协议

书旅

计算机网络 网络 协议栈 OSPF

你问我答:微服务治理应该如何去做?

BoCloud博云

容器 微服务 PaaS API 博云

ARTS打卡 第10周

引花眠

ARTS 打卡计划

技术管理者带团队的几个实用技巧

Phoenix

团队管理 企业文化 团队 价值观

智能膜切机,解决手机贴膜行业难题

Geek_116789

Vue中使用装饰器,我是认真的

前端有的玩

Java Vue 装饰器

在人工智能时代追逐的“后浪”

华为云开发者联盟

程序员 AI 开发者 技术社区 华为云

谈一谈webpack打包

林浩

Java 大前端 webpack

手写一个重入锁

诸葛小猿

synchronized CAS 重入锁 compareAndSwap ReentrantLock

系统设计系列之如何设计一个短链服务

看山

架构 面试 分布式 架构设计 短链服务

《深度工作》学习笔记(3)

石云升

学习 深度工作 工作哲学

云小课 | IPv4枯了,IPv6来了

华为云开发者联盟

IP 公有云 虚拟私有云 华为云 虚拟化

初识分布式:MIT 6.284系列(一)

Kerwin

分布式 MIT 28天写作

Newbe.Claptrap 框架如何实现多级生命周期控制?

newbe36524

架构 微服务 .net core ASP.NET Core

基于 Golang的侵入式 Opentracing实现全链路追踪 ----实践篇

是老郭啊

秒杀系统问题与方案设计

superman

秒杀 架构总结

华为云GaussDB(DWS)内存知识点,你知道吗?

华为云开发者联盟

数据库 大数据 数据 内存 华为云

飞天茅台超卖事故:Redis分布式锁请慎用!

程序员生活志

redis 分布式

什么?不写代码也能做功能开发! -RUOYI 教程二

Java_若依框架教程

Java 无代码开发 若依

授人以渔:stm32资料查询技巧

华为云开发者联盟

架构 armv8 芯片 华为云 二进制

数据人必须知道的SQL概念(A—Z)

大唐小生

sql 数据 职场成长

财务分析与主要的模型

松子(李博源)

老哥,您看我这篇Java集合,还有机会评优吗?

苹果看辽宁体育

Java 后端

微软苏州集体抵制来自阿里、华为的跳槽者:请停止你的“奋斗逼”行为!网友:看到 955 不加班的公司名单,我酸了

程序员生活志

程序员 加班 996

JVM系列之:JIT中的Virtual Call

程序那些事

Java JVM JIT

实用!一键生成数据库文档,堪称数据库界的Swagger

程序员小富

Java MySQL

第九周

hdhdh

将信将疑,将中台进行到底

郭华

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