暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

新优技术之Java模块化

开源中间件 2021-09-10
1901

一 概述


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开发者尽早把模块化能力用起来,也可以对软件应用更好的梳理。

文章转载自开源中间件,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论