模块化是Java9正式引入的feature,全称是:Java Platform Module System (JPMS),该特性的引入增强了Java的模块化和封装性;而且用户可利用JDK9提供的打包工具Jlink
生成可执行镜像文件(该文件可不依JDK环境直接运行)。本文主要涉及到的内容如下:module-info.java
的语法说明,Jlink
, Jmod
, Jdeps
工具的使用;Maven modules
与Java modules
的关系;模块化实操练习DEMO;
Java9模块化代码编写的核心类是module-info.java
,该类必须在模块的根路径上定义(例如:maven项目中可以将module-info.java
放置在main\src\java
下);
当前模块的所有定义都在module-info.java
文件中,主要关键字包括:exports
, module
, open
, opens
, provides
, requires
, uses
(with
, to
, transitive
);
module-info.java
中 模块名称必须定义且moduleName必须保证唯一(模块名可自定义,建议直接用模块包名做模块名),body({}
中的内容)可以为空;
当一个工程中有module-info.java
时,会被当做一个模块来看待,访问外部模块中的类时会受外部模块定义的约束限制(例如:只能访问到其他模块exports
的内容,更强的封装性),当工程中没有module-info.java
时,则当成普通的jar访问;
默认情况下,在模块中的public类需要exports
才能被外部其他模块访问到,exports
的类中用public/protected修饰的嵌套类也可被外部模块访问到;
各关键字使用说明如下:
/**
* 声明模块名称:algorithm.api,open:可选项;
* 当用open修饰module时表示模块中的任何类都可以被外部访问到;
* all packages in a given module should be accessible at runtime and via
* reflection to all other modules
*/
[open] module algorithm.api {
// 声明当前模块依赖于另一模块java.base
// java.base是默认requires,不用在module-info.java中明确声明;
requires java.base;
// 依赖的传递性,与maven的依赖继承性相似,任何依赖algorithm.api的模块同时也依赖java.desktop;
// 去掉transitive则必须在依赖模块中明确依赖,即声明requires java.desktop;
// JavaSE内部module间的依赖都已用transitive修饰,这使得我们对JavaSE module声明依赖更加简洁;
requires transitive java.desktop;
// 对java.xml的依赖在编译期必须,运行期非必须,
// 类似maven中的<scope>provided<scope>,使用Jlink打包的jimage不包含java.xml的文件
requires static java.xml;
// 将当前模块指定包中的public类(包含用public/protected修饰的嵌套类)exports(公布),供给外部模块访问;
// 只exports当前声明的package中的类,子package中的内容不被导出,需另声明;
exports nl.frisodobber.java9.jigsaw.calculator.algorithm.api;
// 将包中的类导出给指定的Modules,只能在限定的Module内使用;
exports nl.frisodobber.java9.jigsaw.calculator.algorithm.api.extension to java.desktop, java.sql, calculator.gui;
// 引入通过 provides…with 提供的service类,一般 provides…with 的定义在其他模块中
// 代码中可以通过 ServiceLoader.load(Algorithm.class); 获取可用的service类;
uses nl.frisodobber.java9.jigsaw.calculator.algorithm.api.Algorithm;
// 通过 provides…with 指令,提供一个实现类;外部模块使用时可用过 uses 引入;
provides nl.frisodobber.java9.jigsaw.calculator.algorithm.api.Algorithm with nl.frisodobber.java9.jigsaw.calculator.algorithm.add.Add;
// 所有包内的public类(包含public/protected嵌套类)只能在运行中被访问
// 包内的所有类以及所有类内部成员都可以通过反射访问到
opens nl.frisodobber.java9.jigsaw.calculator.algorithm.api;
// 限定访问包的模块
opens nl.frisodobber.java9.jigsaw.calculator.algorithm.api.scala to java.desktop, java.sql;
/**
* By default, a module with runtime reflective access to a package can see the package’s public types
* (and their nested public and protected types). However, code in other modules can access all types in
* the exposed package and all members within those types, including private members via setAccessible,
* as in earlier Java versions.
*/
}
可以通过java -h
, javac --help
查看所有与模块化相关的命令;
java --list-modules
:列出可见模块;java -d <moduleName>
or java --describe-module <moduleName>
:描述模块,moddule-info.java
中定义的信息;
打包一个模块
# 首先编译源码
> cd maven-java9-jigsaw
> javac
--module-path fd-java9-jigsaw-algorithm-api/src/main/java
-d classes/api
fd-java9-jigsaw-algorithm-api/src/main/java/nl/frisodobber/java9/jigsaw/calculator/algorithm/api/Algorithm.java
# 其次打包jar文件
# TODO 需将下述cmd替换上一步打编译的内容
> jar --create
--file target/jpms-hello-world.jar
--main-class org.codefx.demo.jpms.HelloModularWorld
-C target/classes
mvn clean package
注:自己手动打包方式比较麻烦,实际项目中直接用 maven or gradle.
java --module-path <path1>[;<path2>...] -m <moduleName>/<mainClass>
--module-path
: 执行模块所处的路径,多个路径用 ;
分割;-m
or --module
:指定要执行的模块 & 要执行的main函数所在的类;Jlink
创建一种名为jimage
的镜像文件,可直接运行,无需JDK环境;jimage
可以有效减小运行时镜像(剔除了无依赖的java module
);Jlink
使用时要求当前项目以及其所依赖的所有jar都有module-info.java
文件(针对无module-inf.java
的jar存在的场景解决的解决方案可关注视频靠后一部分的讲解: https://youtu.be/jpi2i1d7hqc);生成jimage
示例:
> cd maven-java9-jigsaw
> mvn clean package -DskipTests
> jlink
--module-path libs
--add-modules calculator.gui,calculator.cli
--compress 2 // gzip压缩,可选
--output jimage
运行jimage
:
> cd jimage/bin
> java -m calculator.gui/nl.frisodobber.java9.jigsaw.calculator.gui.Main
// TODO diff between jmod and jar, how to use jmod;
生成jmod文件:
# 将jar转换成jmod文件
> cd libs
> jmod
create calculator.gui
--class-path fd-java9-jigsaw-gui-1.0-SNAPSHOT.jar
--module-version 1.0
依赖对象分析工具,可分析模块相关依赖信息,具体命令可通过jdeps -h
查看,简单示例:
> cd libs
> jdeps
--module-path libs
-m calculator.gui
--list-deps
name | description |
---|---|
jlink | Build Java Run Time Image |
jmod | Build Java JMod files |
java9 module system
的功能是模块的管理(定义&继承&类访问合法性约束);java9 module
则特指module-info.java
中定义的模块;java module
的工程来讲,maven仍是作为jar包管理&打包的工具,但java模块间的依赖管理&类访问约束是依赖java module system
管理;plugin
用于方便的编译&打包java module
,但是alpha
版本,尚未release GA
;JPMS
vs maven
的传送门:
https://stackoverflow.com/questions/39844602/project-jigsaw-vs-maven可参照工程:https://github.com/itinycheng/maven-java9-jigsaw
本章主要目的是讲解模块化的入门实操和模块化相关的工具使用,经过本章的学习相信大家可以顺利的在自己项目中引入模块化开发。为更方便直观的学习,我Fork了一个比较好的模块化Demo,并修改了原项目中的错误 & 升级Java版本到11,大家可以在该项目基础上做些实操练习,巩固所学,项目地址: https://github.com/itinycheng/maven-java9-jigsaw。
addition:
JPMS
特性引入之后,Java
在运行时也能获取Module
相关信息,具体可关注Class.java
中新增的变量/方法,比如:clazz.getModule()
,clazz.forName(module, name)
, etc.