# java9 **Repository Path**: lifutian66/java9 ## Basic Information - **Project Name**: java9 - **Description**: java9模块化践行 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-11-21 - **Last Updated**: 2022-11-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 模块化服务践行 ### 1.背景 #### 1.1背景 ​ 随着java版本发布,jdk9带来了很多新特性,其中一个特性是模块化,提供了一个模块化的jdk和模块化系统。你是否有过这样的困,代码为什么在这里?该从哪里找到?这个就是缺乏结构性。什么是模块化?**模块化**是将系统分解成**独立**的且**互相连接**的模块的行为,以减少项目因缺乏结构性带来复杂性和管理的难度,是项目更加易扩展和健壮;避免依赖循环带来的诸多问题(依赖版本冲突,项目臃肿,依赖管理困难);可按需打包项目,将不需要的依赖从jdk层开始剔除,避免资源浪费,最重要的是减少攻击面,防止漏洞攻击;解决类路径不安全的问题(无限制的反射); ### 2.模块化 ​ 模块化是自jdk9开始的新特性,所以首先需要安装jdk9,下载地址放在文末附录。 ​ 2.2 由hello项目入手品略模块化项目的编译、打包、运行、生成运行时环境的过程,深入理解模块化的按需打包的优点 ​ 2.3 项目完整展示了模块化,模块之间的依赖使用、对外开放服务、对外允许反射的服务、以及隐式的依赖传递,深入展示模块化的各个模块使用基本要点。 #### 2.1模块化jdk ​ 首先我们看一下安装好的jdk,如下图1所示,jdk分成了多个模块,每个模块化下都有个module-info.class,图2所示为jdk8的目录,是多个jar。module-info 就是模块化的声明文件,文件需要定义模块的名字、依赖的模块、对外开放的类、接口实现类... 由此可见,jdk9没有层级区分,事实证明其打破了双亲委派机制,变成了模块化加载,每个模块根据 module-info 中的信息,进行加载,效率更加高效。下面进行模块化的第一次尝试。 ![image-20221121101108918](https://gitee.com/lifutian66/img/raw/master/img/image-20221121101108918.png) ​ **图1** ![image-20221121101356390](https://gitee.com/lifutian66/img/raw/master/img/image-20221121101356390.png) ​ **图2** #### 2.2模块化项目 ​ 着重展示模块化项目从建立到可运行环境输出过程,项目名为hello,项目目录如下图3 ![image-20221121105650496](https://gitee.com/lifutian66/img/raw/master/img/image-20221121105650496.png) ​ **图3** src目录下新建一个module-info.java,模块名是hello。在hello目录下,新建Main.java,添加代码代码,其实就是打印一个hello world。下面进行编译,运行,可运行环境输出。 ```java public static void main(String[] args) { System.out.println("hello world"); } ``` **编译** 编译java文件,out是个目录,编译生成文件到out这个目录下 ```sh javac -d out .\src\hello\Main.java .\src\module-info.java ``` **打包** 将out目录下打包成 hello.jar 文件,存放在jar目录下,并指定应用程序入口点为 hello.Main ```shell cd .\out\ mkdir jar jar -cfe hello.jar hello.Main * ``` **运行** 运行生成的jar ,--module-path指定模块路径, jar是存放hello.jar文件的目录,控制台输出 hello world ```shell java --module-path .\jar\ --module hello/hello.Main 或者 java --module-path .\jar\ --module hello ``` **生成模块** 指定生成模块的jar是hello.jar,生成模块 hello.jmod ```shell jmod create --class-path hello.jar hello.jmod ``` **生成运行环境** 将hello.jmod 放到jdk安装目录下的jmods目录下(windos下module-path指定多个路径分隔符是分号;linux分隔符是冒号:我的环境是windos,尝试多次均为未成功,所以粘贴这个模块到jdk的基础模块中,指定module-path 为当前目录即可)并在此目录执行以下命令,指定模块路径为当前目录,--add-modules添加java.base和hello模块 ,--launcher定义一个入口点直接运行模块 --output 指定生成的运行时环境的目录名称 ```shell jlink --module-path . --add-modules java.base,hello --launcher hello=hello --output jre/ ``` **运行** 打开生成文件目录,可以看到如图4所示,bin目录下生成可运行hello和 hello.bat,windows下命令行运行 .\hello.bat,控制台打印,hello world。 ![image-20221121122705871](https://gitee.com/lifutian66/img/raw/master/img/image-20221121122705871.png) ​ **图4** **总结** 以上项目生成的文件是一个完整的可运行的Java运行环境即Java Runtime Environment 即jre,而这个可运行的环境大小只有35.9 MB,完整的jre是215M(我的环境中),这也就是模块化的一大优点,可按需打包依赖,从jdk层支持,应用依赖也可以按照如此按需打包,减少浪费资源,以上只是模块化从编译到生成jre的过程,下面我们进行模块化的完整项目开发。 #### 2.3模块化应用 着重展示模块化的使用以各关键字的解释。 假设场景,模拟每天工作的情况,新建项目,新建四个模块,eat、transportation、work、console 项目如下, eat模块模拟吃饭喝水,transportation模块模拟交通,work模块模拟工作,console 模块模拟一天的生活 ![image-20221121142358791](https://gitee.com/lifutian66/img/raw/master/img/image-20221121142358791.png) ​ **图5** **1.eat模块** eatapi目录下,对外提供服务接口,吃饭喝水两个方法 ```java public interface EatApi { void eat(); void drink(); } ``` eatservice目录下,实现EatApi接口 ```java public class EatApiImpl implements EatApi { @Override public void eat() { System.out.println("吃饭了"); } @Override public void drink() { System.out.println("喝水了"); } } ``` 模块化 module-info 类,定义名称为eat,**exports**对外暴露eatapi接口,接口的实现为EatApiImpl类,**provides with** 可被**ServiceLoader**根据SPI的方式加载到,但是反射并不能获取实现类。 ```java module eat { exports eatapi; provides eatapi.EatApi with eatservice.EatApiImpl; } ``` **2.transportation** transportapi目录下,对外提供服务,模拟交通 ```java public interface Transportation { void transport(); } ``` transportservice目录下,实现transportapi接口 ```java public class TransportationImpl implements Transportation { @Override public void transport() { System.out.println("开车出去"); } } ``` 模块化 module-info 类,定义名称为transportation,**exports**对外暴露transportapi接口,接口的实现为TransportationImpl类,**opens**关键字,可以加在module关键字之前,表明整个模块都可以被深度反射,opens transportservice 只表明该包下的类可以被深度反射。 ```java module transportation { exports transportapi; provides transportapi.Transportation with transportservice.TransportationImpl; opens transportservice; } ``` **3.work** workapi目录下,对外提供服务,模拟工作 ```java public interface Work { void work() throws Exception; } ``` workservice目录下,实现接口,通过ServiceLoader获取eat模块EatApi,通过反射获取 Transportation实现了类 ```java public class WorkImpl implements Work { @Override public void work() throws Exception { System.out.println("开始工作了"); //获取服务 EatApi eatApi = ServiceLoader.load(EatApi.class).findFirst().get(); //喝口水 eatApi.drink(); //反射获取 Transportation实现了类 Transportation transportation = getTransportation(); //出去一趟 transportation.transport(); //吃点东西 eatApi.eat(); //喝口水 eatApi.drink(); } private Transportation getTransportation() throws Exception{ Class transportationClass = (Class) Class.forName("transportservice.TransportationImpl"); Transportation transportation = transportationClass.getDeclaredConstructor().newInstance(); return transportation; } } ``` 模块化module,workapi可对外暴露,实现类是WorkImpl,**requires** 表示依赖模块, 依赖模块eat、transportation,调用了这两个模块的服务,**transitive** 关键字表示该依赖会被传递,引用本服务的服务也会引用transitive修饰的模块,不用在主服务中在引一次,**uses**表示使用模块中的具体服务 ```java module work { exports workapi; provides workapi.Work with workservice.WorkImpl; requires transitive eat; requires transitive transportation; uses eatapi.EatApi; } ``` **4.console** 该模块调用work模块以及work **transitive** 的模块 模块化配置如下,依赖模块work,使用workapi.Work和eatapi.EatApi ```java module console { requires work; uses workapi.Work; uses eatapi.EatApi; } ``` day1目录下新建Main,模块Work的依赖隐式传递,最终打印出结果 图6所示。 ```java public class Main { public static void main(String[] args) throws Exception { //获取work 服务 ServiceLoader load = ServiceLoader.load(Work.class); Work work = load.findFirst().get(); //调用 work.work(); //其他服务 ServiceLoader eatLoader = ServiceLoader.load(EatApi.class); EatApi eatApi = eatLoader.findFirst().get(); eatApi.eat(); eatApi.drink(); //反射获取 Transportation transportation = getTransportation(); transportation.transport(); } private static Transportation getTransportation() throws Exception { Class transportationClass = (Class) Class.forName("transportservice.TransportationImpl"); Transportation transportation = transportationClass.getDeclaredConstructor().newInstance(); return transportation; } } ``` ![image-20221121160818340](https://gitee.com/lifutian66/img/raw/master/img/image-20221121160818340.png) ​ **图6** ### 3.总结 ​ 以上便是使用模块化生成需要jre环境和在项目中使用多模块服务的践行。 ​ 模块化核心原则模块必须**强封装性**,隐藏部分代码,只对外提供指定服务,也就需要**良好的接口定义**并且**显示依赖**,声明式的服务依赖,不是使用了但不知道依赖来自哪里的糊涂账。可以提高模块的可读性,明确服务的入口和依赖,减少服务循环依赖,按需打包,解决反射带来的全可见危害,提高安全性。但是就目前而言模块化带来的收益远低于迁移工作,目前大家都在spring的全家桶应用项目,使用很方便,但是真正按照模块化将其切分出来,并且能够完全理清楚项目依赖,也是很难的,不过模块化的方法和工具,jdk已然提供,模块化的思维和想法是很值得学习的,相信在不久的将来,模块化会更智能和完善吧。 附录 项目hello https://gitee.com/lifutian66/java9/tree/master/hello 项目java9 https://gitee.com/lifutian66/java9/tree/master/java9 生成hello.jmod https://gitee.com/lifutian66/java9/hello.jmod 生成jre https://gitee.com/lifutian66/java9/tree/master/jre jdk9 地址:https://www.oracle.com/java/technologies/javase/javase9-archive-downloads.html 参考文档: java9模块化开发核心原则和实践