Java9模块化学习

1. 什么是模块化

本质上,模块化就是将系统分解成独立且相互连接的模块的行为。java9模块除了包含代码外,一个重要特征是增加了描述模块的文件:module-info.java

Q:为什么这么做?-- 减少复杂性

Project Jigsaw

Jigsaw 项目从2008就开始对JDk模块化进行探索,2014年开始进行设计和实现。

2. 模块化JDK

JDK由90个左右的平台模块组成,每个模块都是一个定义好的功能块,下图显示了部分模块的子集以及依赖关系:


image.png
  1. 顶部的两个重要模块:java.se.ee,java.se,主要用于对其他模块进行逻辑分组,属于聚合器模块。

  2. 每个模块都隐式依赖于java.base,因为该模块公开了java.lang, java.util之类的包。

  3. 模块之间依赖关系没有循环,java模块系统不允许编译时存在模块循环依赖。

    列出所有JDK模块: java --list-modules

    查看模块定义:java --describe-module java.rmi

3.模块如何定义

模块描述文件module-info.java:

module com.test{// 模块名称
    requires java.sql;  //依赖java.sql模块,普通依赖
    requires transitive java.xml;  //依赖java.xml,可传递依赖
    exports com.my.test1;  //导出com.my.test1包到其他模块
    exports com.my.test2 to xx.xx.xx; //限制导出,导出com.my.test2到指定模块
}
  • 可读性:模块A依赖模块B,意味着对模块B可读,也就是可以访问模块B导出包中的类型。普通依赖可读性是不可传递的。
  • 可访问性:public、protected、private等访问修饰符的可访问性规则依旧适用。

4.模块Demo

单个模块

创建模块

编译

mkdir -p mods/helloworld
javac -d mods/helloworld helloworld/src/helloworld/module-info.java helloworld/src/helloworld/com/module/helloworld/HelloWorld.java

打包

mkdir mlib
方式1:jar -cfe mlib/helloworld.jar com.module.helloworld.HelloWorld -C mods/helloworld .
方式2:jar --create --file=mlib/com.greetings.jar --main-class=com.module.helloworld.HelloWorld -C mods/helloworld .

运行

1.以编译后的文件运行模块:

java --module-path mods --module helloworld/com.module.helloworld.HelloWorld

2.以模块化jar包运行模块:

java -p mlib -m helloworld

多个模块

模块间的引用

用Maven如何配置多个模块

1.为每个模块添加maven依赖,maven只是配置模块路径。

2.pom文件中配置编译器插件为java9或以上

 <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>10</source>
          <target>10</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

3.给每个模块添加module-info.java,并将依赖项作为requires语句添加。

模块定义限制

模块解析是一个递归的过程,从根模块开始解析->requires/requires transitive所需的模块->循环直到没有其他任何依赖项。

  • 模块名称必须唯一

  • Java模块系统不支持编译期循环依赖

  • 只有一个模块可以导出一个给定的包

  • 模块不能定义版本,版本信息必须存储在模块之外。如Maven通过pom文件选择正确的模块版本并设置到模块路径上

image2.PNG

5. 服务

Java模块系统的服务机制可以共享公共接口,并将实现代码强封装到未导出的包中,实现服务提供者和消费者的真正解耦。

module api {
    exports com.calculate; //api模块导出服务接口
}
//Caltulator接口代码
public interface Calculator {
    String getName();
    BigDecimal calculate(BigDecimal v1, BigDecimal v2);
}
module provider1 {
    requires api;
    provides com.calculate.Calculator with com.provider.Sum;//该模块提供了Calculator接口的一个实现,并且不导出实现类
}
//Sum实现类代码
public class Sum implements Calculator {
     ......
}
module com.consumer{
    requires api;
    uses com.calculate.Calculator; //该模块想要消费Calculator的实现
}
消费端代码:
public static void main(String[] args) {
        ServiceLoader<Calculator> services = ServiceLoader.load(Calculator.class);
        for (Calculator cal : services) {
            cal.getName();
        }
  }
  • uses子句不要求Calculator实现在编译期间可用,消费者和提供者在运行时才被绑定在一起,由此实现了消费者和提供者的完全解耦,提供了不用重新编译、打包的可扩展性

  • 消费者使用ServiceLoader获取提供者实现对代码是有一定侵入性的,有两种改进的方式:

    • 在api模块接口中添加一个静态工厂方法,该方法返回所有提供者实现

      module api1 {
          exports com.calculate; //api模块导出服务接口
           uses com.calculate.Calculator; //该模块想要消费Calculator的实现 
      }
      //api模块接口代码
      public interface Calculator {
          String getName();
          BigDecimal calculate(BigDecimal v1, BigDecimal v2);
          static Iterable<Calculator> getCalculator(){
              return ServiceLoader.load(Calculator.class);
          }
      }
      
      module com.consumer1{
          requires api;
      }
      消费端代码:
      public static void main(String[] args) {
          Iterable<Calculator> services = Calculator.getCalculator();
        }
      
    • 使用开放模块和开放包,消费者通过@Inject依赖注入服务提供者类。

      open module api{// 开放模块中的所有包,任何模块都可以反射访问导出、未导出的类型的私有域
          exports com.api;// 未导出包在编译时依旧是不可访问的
      }
      module api{
          exports com.api;
          opens com.api;
      }
      

6.现有代码使用Java9

将现有代码迁移到Java9可以分两步:

在Java9上构建和运行现有代码,无需模块化

  • Java9有一个特殊的未命名模块(unnamed module),该模块可以读取其他所有的模块。在模块之外编译和加载的代码都在未命名模块中。

  • 由于JDK是模块化的,对类进行了强封装,通过反射访问这些类型时,会有警告。

    (java9中exports导出的公共类型,反射访问该类型的私有域是禁止的,为了保持兼容性,默认运行时访问可以通过java选项--illegal-access=值,控制,默认值为permit )

  • 编译和运行未命名模块时,以java.se作为根模块,如果程序使用了java.se.ee下的模块,会报错。可以使用--add-modules添加相关模块。

image3.PNG
  • 如果代码使用了已经删除的类型,改代码。可以使用JDK附带的一个工具jdeps查找在使用的已经被删除或封装的JDK类型。

将代码模块化

其实规模比较小的程序,还有比较老的系统(没有太多更新,只是维护改bug)没必要模块化。程序规模比较大而且更新比较多的话,可以考虑迁移,因为模块化是可以提高可维护性和可重用性的。

将现有代码模块化最大的问题:如何迁移第三方库,大部分第三方库都只是普通Jar文件,不是模块。

  • 创建自动模块:将现有jar文件放到模块路径,不用改变任何内容,就可以创建一个自动模块

    • 自动模块会导出所有包
    • requires transitive 所有其他已解析的模块
  • 添加该第三方库的包到被依赖的模块描述文件中

总结

模块化的好处

  • 模块描述文件,编译时就能检查模块所有依赖,减少运行时错误
  • 强封装,模块需要显示的配置向其他模块导出的内容,内部实现细节完全不可见
  • 提高可维护性
  • 提高安全性
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335