java-modularity
CONCEPT
在java9,java引入了一种不同于,或者可以说是在
package
之上的一个新的抽象层–> JPMS。什么是module。 module是一系列相关的package与resource的集合
module的两种表现形式:
jar
与已编译项目
Module-Properties
name
: Module的name
命名方式与package
相同,goupId
反写。requires | uses
Module所依赖的其他模块 | 服务。所有模块都直接或者间接依赖java.base
。exports
Module公开的可以被其他模块依赖的 packageprovides
Module 提供的服务open<module level> | opens<package level>
其他module对私有包的反射权限
Module-Type
- 系统Module: 包括根据javaSE标准实现的
java
开头的module和 jdk内部使用的module - 应用级别Module: 具有
module-info.java
描述文件的我们自定义与第三方jar或者编译项目 - 自动Module: 被添加在
--module-path
模块路径上的没有module-info.java
的jar
,他们通常取example-1.0-GA.jar
中的example
作为module名,可以用jdeps
查看到 - 未命名Module: 被添加在
--class-path
类路径上的class
或者jar
都被加到Unnamed Module
module-info.java
参考 baeldung-java-modularity-module-declarations
Examples
Simple-Multiple-Modules
参考 baeldung
Maven-Multiple-Modules
Cteate-Maven-Project
jdk为openjdk17。尝试构建带有其他依赖的maven模块化项目,包括未模块化的依赖与已经模块化的依赖。
Project-Struct
与之前不同的是,在java
源码目录下,多了module-info.java
文件。
pom.xml
1 |
|
由于是模块化的project,所以如果依赖不仅要在pom.xml
中声明引入,并且只要创建了 module-info.java
文件,就要在其中写入相关配置,否则会编译失败。
module-info.java
1 |
|
Main.java
1 |
|
目前看起来没有任何问题,然后编译
Compile
在模块根部录下:
javac -d .\target\classes -p "D:\maven_local_repository\com\alibaba\fastjson2\fastjson2\2.0.10\fastjson2-2.0.10.jar;D:\maven_local_repository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar" .\src\main\java\module-info.java .\src\main\java\world\langyu\provider\*.java
其中
-d
选项指定编译的输出目录。-p/--module-path
选项 指定应用代码依赖的外部模块的路径,通常是jar包路径。注意在windows下,参数要用""
包起来,并且多个路径之间用;
隔开。- 由于是单模块,后跟
module-info.java
文件的路径。 - 最后列出要编译的
.java
文件的路径
emmmm,不出意外的话就要出意外了=_=
。
1 |
|
顺着这个报错就找到了fastjson2
的github的issue,然后是他们的一个bug,本来生个版本就可以解决了,但我想看看到底是个什么问题->fastjson2-issue
维护的人修改这个bug是去修改了core/pom.xml
原来 fastjson2
使用的一个第三方插件来做的模块化,它的编译产物还是java8的。
换完版本之后顺利编译
因为我们要依赖一些静态配置资源,copy过去。
运行
Non-modular-Mode
执行: java -cp ".\target\classes;D:\maven_local_repository\com\alibaba\fastjson2\fastjson2\2.0.15\fastjson2-2.0.15.jar;D:\maven_local_repository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar" world.langyu.provider.Main
cp
指定所有涉及到的类路径。- 最后带上主类的全限定名。
run-as-simple-jar
pom.xml
中配置mainclass
1 |
|
首先要打成jar
包->mvn clean package
执行:java -p "D:\maven_local_repository\com\alibaba\fastjson2\fastjson2\2.0.15\fastjson2-2.0.15.jar;D:\maven_local_repository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar" --add-modules com.alibaba.fastjson2,org.mybatis -jar .\target\moduleA-1.0-SNAPSHOT.jar
注意:
-p
指定所依赖的jar
的路径,-p
的全称是--module-path
,虽说是module
,但是 JPMS中,module
的分发产物依然是jar
形式。fastjson2
是个模块化的jar,可以在fastjson2-2.0.15.jar!\META-INF\versions\9\module-info.class
找到模块描述文件,里面自然有module-name,所以在后面--add-modules
指定的参数中就可以指定其定义的com.alibaba.fastjson2
。但奇怪的是,mybatis
分发的jar中我并没有找到module-info.java
,这个名称我是从在编写moduleA
的module-info.java
文件时,idea
提示我的所要requires
的module-name,但按理来说,如果你没有模块化,那就会转为Automatic-Module
,模块名默认是xxxlib-1.0-xx.jar
中的xxxlib
,也就是版本号前面的内容,但这时的mybatis
并符合预期。
于是我顺藤摸瓜,去翻了mybatis
的jar,然后在其中的META_INF/MANIFEST.MF
中找到了
有种解密的味道哈哈哈—~~~
原来mybatis
这个编译的版本是jdk17
,看来mybatis
模块化的做法很简单:
利用了 Automatic-Module机制:
- 它没有
module-info.java
文件,当被其他开发者作为依赖添加到模块路径上(-p
)时,他会成为一个自动模块:1.export and open all packages
2.have full read access to every other module loaded by the module path
- 通过在
MANIFEST.MF
文件中指定Automatic-Module-Name
来覆盖默认的module-name。 - 要做到
2.
可以在pom.xml
中配置maven-jar-plugin
1 |
|
然后其他模块就可以用${module.name}
来生命对这个模块的依赖。
顺带提一嘴,我在测试的时候发现,idea
可以在你指定了Automatic-Module-Name
或者这个模块的 artifactId
(这个是因为如果不指定jar的名称,默认是${artifactId}-{version}.jar
)然后reload maven projects
后 提示或识别出来的module-name。
一直很想找到能这样做的出处(在MANIFEST.MF
中指定 Automatic-Module-Name
),推测是JEP
讨论出来的。
- mail.openjdk-how to name modules, automatic and otherwise
- Automatic module names
- maven-jar-plugin
- dev.java
编译与运行中有关Unnamed-Module的问题
什么是Unnamed-Module
被添加到–class-path中的classes与jar都会被加到Unnamed-Module中。
Unnamed-Module具有如下特性:
- Export and open all its packages to every other module, like the Automatic Module.
- Other module can only access classes in Unname-Module by reflection
- The declaration of dependencies by the unnamed module on other modules is futile at runtime.
未命名模块就是所有放在-classpath
上的类、模块的一个大杂烩模块。因为是未命名
,所以不能被其他模块直接依赖,其他模块只能通过反射的方式访问其中的类,对应1、2
两点。
关于第三点:未命名模块中对外部声明的依赖(即使是在module-info.java
中生命的 require
)不会被模块系统自动解析和加载,除非此时未命名模块是root module
其中之一。
问题复现
比如:现在有A,B两个jar,jarA,jarB都具有module-info.java,也就是都是模块化后的jar,jarB代码层面只依赖于jarA()。
jarB:
1 |
|
jarA:
1 |
|
执行: java -classpath D:\source-read\java-module\moduleA\target\moduleA-1.0-SNAPSHOT.jar -p "D:\source-read\java-module\moduleB\target\moduleB-1.0-SNAPSHOT.jar;D:\maven_local_repository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar" -m moduleB/world.langyu.consumer.Main
注意此时我们将 jarA放在了 类路径上,所以jarA是Unnamed-Module的一部分。所以即使moduleA声明了对mybatis的依赖,但还是会得到如下报错:
1 |
|
问题分析
在启动参数上加上--show-module-resolution
来查看 modules resolved during app startup。
1 |
|
看来mybatis的模块并没有被解析加载到运行时环境中。
这就要提到java模块化系统中的模块发现机制(module resolution)
- java.base是所有模块的根模块,不需要显式声明。
- 编译与运行中,模块系统需要先确定一个或者多个root-module,然后根据这些root-module的依赖关系去
-p
指定的模块路径下寻找所有可以观察到的模块(observable module)
然后解析加载以满足依赖,再加上-cp
指定的类路径,就构成了编译与运行时环境。 - 通过
-m
方式启动,-m
指定的模块就是root-module之一。 - 如果是传统方式
java [options] <主类> [args...]
,则root-module 就是java.*
(java.base
、java.sql
…)模块,也就是JRE
。
在这个例子中(通过-m
),moduleB
是root-module,moduleB、java.base、jarA
构成了运行时环境(注意即使mybatis
模块也在模块路径上,但运行时环境不包含他。),因为jarA
被添加到了类路径上,是个Unnamed-Module,不会有其它模块对他有显式的依赖,并且他也不可能是root-module,所以是不会去解析Unnamed-Module的依赖关系的,自然mybatis
模块没有被加入到运行时环境中。
解决方案
这时就需要手动把org.mybatis
从模块路径上添加到被解析模块中:-add-modules org.mybatis, java.sql
。(mybatis模块还依赖于java.sql
)
或者
直接采用传统方式启动,把类和jar都放在类路径上:java -classpath "D:\source-read\java-module\moduleA\target\moduleA-1.0-SNAPSHOT.jar;D:\source-read\java-module\moduleB\target\moduleB-1.0-SNAPSHOT.jar;D:\maven_local_repository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar" world.langyu.consumer.Main
结束哩 😹
其他相关