一 概述
Java模块化技术在Java9发布时被加入,这个特性可以说是Java语言开发过程中被拖延时间最长的重要功能。原来预期加入Java7,而最终推迟了两个大版本。
OSGi技术中包含了模块化功能,应该说OSGi的模块化特性设计还是很不错的,对包隔离,包之间的依赖性传递等主要功能点定义的很全面,只是通过给MENIFEST文件加入说明,对模块的定义非常的冗长,使得开发者难以学习使用。
而Java模块化能力是应用服务器开发中重要的核心功能,Glassfish就是使用OSGi的模块化能力。
JBoss后来借鉴OSGi的思路,开发出来JBoss Module组件,通过定义模块目录层次结构,以及加入module.xml文件,定义了JBoss应用服务器的模块化能力。
(对于OSGi, Java模块化, JBossModule之间的关联关系和差别,可以看我在QCon 2016 上海站的主题演讲 http://www.infoq.com/cn/presentations/the-evolution-of-java-modular-technology)

业界非常期待Java语言自身的模块化能力。当Java应用系统复杂到一定程度,或像应用服务器这样可以容纳多个应用同时部署情况下,管理Java应用中的jar就不容易了,成百上千的jar会出现版本冲突,依赖混乱等情景。
尽管使用了各种成层次的classloader方式,还是难以理顺较大型应用中模块之间的关系。
Java9发布时,模块化作为重要的新特性终于隆重出场了。这个特性命运多舛,因为对模块化JSR的意见不同,JCP各个厂商投票反对,直接导致Java9在推迟发布后再次推迟,最终才在17年9月发布。
而且经过一年时间,模块化特性并没有被广泛采纳使用。我认为主要有三个原因:
容器时代到来了,大量的新开发应用都采用微服务的方式开发,遭遇jar冲突等情景就大大减少。这样原本在企业应用时代,应用服务器中非常重要的模块化技术,在容器时代似乎也没有那么重要了。
Java9 和 Java10都不是LTS版本,大量的开发商都还在使用Java8作为主力语言。
主流框架如SpringFramework, Hibernate等社区对模块化技术的采用不是很积极,所以开发的应用依赖和过去没有什么差别。
模块化究竟是重要的Java特性,厘清模块化之间的关系,对于发布独立的Java应用镜像,或者native本地化(后续会专门写文章讲述这两个技术)都是重要的前提。
另外微服务架构也不是适合所有的软件应用,很多企业级应用还是部署在应用服务器上最为合适。模块化技术始终会成为每个Java软件工程师必须学习的知识点。
二 Java包组织方式
Java开发源代码,有类,接口,枚举等,有几种开放程度定义,如public, protect, private等,这些都被打包成一个或者几个jar文件。
所有打包在jar的类型都可以被其他应用访问,即使用private,也有办法可以获取信息。开发者为了编程方便,往往无任何限制的访问调用其他包的代码。
Java在加载类,通过classloader,首先加载在类路径中遇到的类型,不会区别是否有访问限制或者软件版本。
多数Java开发者都遇到过Jar-hell问题,比如找不到类,或者是连接出错等问题。
这个问题的基本原因是应用application和包jar之间缺乏一个层次定义,而模块module/bundle就是弥补这个缺失。
加入模块这一层级之后,应用就可以用模块这个级别来管理。
Java9以后的JDK,目录结构和原来大为不同,多了一个jmods目录,里面容纳了很多后缀为jmod的文件。
其中最重要的就是java.base.jmod文件,这是Java最基本的模块,包含所有基础的包,如java.lang, java.net, java.io, java.util等。
原来JDK中所有的都组织成jmod文件,如日志处理有java.logging, 数据库JDBC是java.sql, 等,还有java.se这样的容纳多个jmod等组合。通过模块化组织,我们就可以很容易的自行裁剪出需要的JDK。
JDK11中去除了JavaEE,Corba等内容,就是直接排除了相关的jmods就可以。
对于JDK来说,不光是最后的二进制文件,源码组织,文档javadoc等也都按照jmod对应的名字组织了。
通过jmod命令,可以查看mod文件中的内容
/x1/java/jdk11/jmods$ ../bin/jmod list java.logging.jmod
classes/module-info.class
classes/java/util/logging/LogManager$4.class
classes/java/util/logging/LogManager$LogNode.class
classes/java/util/logging/FileHandler$1.class
classes/java/util/logging/Logger$LoggerBundle.class
classes/java/util/logging/LogManager$7.class
...
jmod文件其实就是一个压缩文件
/x1/java/jdk11/jmods$ jar -tvf jdk.editpad.jmod
406 Wed Aug 22 18:56:56 CST 2018 classes/module-info.class
1275 Wed Aug 22 18:56:56 CST 2018 classes/jdk/editpad/resources/l10n.properties
6068 Wed Aug 22 18:56:56 CST 2018 classes/jdk/editpad/EditPad.class
766 Wed Aug 22 18:56:56 CST 2018 classes/jdk/editpad/EditPad$1.class
jmod文件中不但包含了class文件,也有bin目录下的执行文件,各种资源文件,版权定义文件,还可能会有本地库文件如so,dll等。
目前JDK文档中,都包含了模块依赖图,如java.sql模块

三 模块之间的关系定义
模块中使用module-info.java来定义,使用jmod可以看到描述信息
/x1/java/jdk11/jmods$ ../bin/jmod describe java.logging.jmod
java.logging@11
exports java.util.logging
requires java.base mandated
provides jdk.internal.logger.DefaultLoggerFinder with sun.util.logging.internal.LoggingProviderImpl
contains sun.net.www.protocol.http.logging
contains sun.util.logging.internal
contains sun.util.logging.resources
platform linux-amd64
这个命令其实看到的就是module.info.java的内容,可以看到是由若干行模块内容描述构成。
其中exports, requires, provides, contains, platform等是关键字,另外还有use, open等。
requires 表示这个模块需要依赖哪个包,base包是基础肯定是需要依赖的
exports 表示这个模块可以提供什么给其他模块使用,这里定义了java.util.logging,就是日志模块的主要对外暴露的功能
provides with 表示这个模块提供了哪个Service能力,我们知道Java开发中经常使用ServiceLoader来加载只使用接口,而实现是通过声明来提供的类。这里DefaultLoggerFinder就是接口,而LoggingProviderImpl就是实现类
contains 表示这个模块中还有哪些类包,但并不对外暴露
platform 表示是linux amd6架构体系的
use 说明应用系统可以使用哪个Service接口
open 表示对外暴露的功能还允许通过反射访问,很多开发框架都需要这个能力
模块export对外暴露可以使用to关键字定向限定只有某一个模块才可以访问这个模块
如果使用transitive关键字,表示可以传递对外暴露能力。
四 命令行工具对模块化的支持
JDK9以后,主要的java命令行工具已经都提供了对模块化能力的支持,比如我们定义了一个简单的java类:
package example;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Modular World!");
}
}
同时提供一个module-info.java文件(暂时为空)
module example {
}
编译
javac example/module-info.java example/HelloWorld.java
执行
java --module-path example --module example/example.HelloWorld
可以看到如果采用了模块化能力,需要加入模块化相应的参数,但和类路径一样的,只要充分了解应用内路径结构就不会很麻烦。另外实际情况中打包和运行通常都是包管理来自动完成,如maven。
采用了模块化以后,开发者在一段时间会觉得不太方便,比如经常会找不到对应的类,编译运行也会有种种阻力。
但如果只是贪图一时顺畅,无限制的引入和使用三方包功能,后续会给维护和运维带来很多烦恼。
所以我建议Java开发者尽早把模块化能力用起来,也可以对软件应用更好的梳理。




