当前位置:   article > 正文

Java 模块化开发_java模块

java模块

前言

        之前在 Github 下载的好多代码发现都是 Java 模块化开发出来的,模块化是 JDK9 引入的,所以在 JDK9 及其后续的版本中,都可以采用模块化开发的方法来进行项目的开发。尤其是Java桌面应用开发,虽然这只是我的一个业余爱好,但是多学点技术没什么坏处。

1、Java 模块化开发

1.1、概述

        在 JDK9 之前,无论是运行一个大型的软件系统,还是运行一个小的程序,即使程序只需要使用Java的部分核心功能, JVM也要加载整个JRE环境。所以为了解决这个问题,让Java实现轻量化,Java 9正式的推出了模块化系统。Java被拆分为N多个模块,并允许Java程序可以选择的加载模块,这样就可以让Java以轻量化的方式来运行。

        Java 模块化对于开发桌面软件的好处是非常大的,之前写一个不管多简单的 JavaFX 应用,要想在所有没有 Java 环境的机器上运行必须把 JRE 也打包到软件里面,但是有了模块化,我们只需要把用到的类库打包即可。

1.2、模块的引入和模块内包的导出

        模块化最鲜明的特征就是需要在代码目录下创建一个名为 "module-info.java" 的模块描述文件,这个文件定义了该模块需要引用的其它模块、暴露模块中的哪些包给外部哪些模块、提供给外部模块哪些接口服务、使用了其它模块的哪些接口服务等。

1.2.1、模块的导出

下面我们新建一个 Maven 项目,先创建三个模块:

在 module1 下的 com.lyh.domain 包下创建一个类 User:

  1. package com.lyh.domain;
  2. public class User {
  3. private String name;
  4. private int age;
  5. public User(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. public User(){
  10. }
  11. public void sayHello(){
  12. System.out.println("Hello " + name);
  13. }
  14. @Override
  15. public String toString() {
  16. return "User{" +
  17. "name='" + name + '\'' +
  18. ", age=" + age +
  19. '}';
  20. }
  21. public String getName() {
  22. return name;
  23. }
  24. public void setName(String name) {
  25. this.name = name;
  26. }
  27. public int getAge() {
  28. return age;
  29. }
  30. public void setAge(int age) {
  31. this.age = age;
  32. }
  33. }

1.2.2、模块的导入 

现在我们希望在模块2中可以调用它,所以我们需要在 module1 下的 java 目录创建 module-info.java:

  1. module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B; // 用 to 指定只能给 B模块使用
  7. }

        这里我们定义 module1 的模块名为 A,这其实是不影响的,只要保证我们的项目中没有重名的就可以。现在我们希望 module2 可以使用 module1 中的 User 类,所以我们需要 module1 暴露它的 com.lyh.domain 包。至于上面的 require java.base 其实是每个模块默认都会导入的包,我们也可以省略。

        这里还需要说明的是,默认暴露的包都是允许其它模块通过反射来访问该包下用 public 修饰的内容的

module1 的模块描述文件配置完毕之后,我们需要配置 module2 的:

  1. module B {
  2. // 引入A模块暴露的包并传递依赖
  3. requires transitive A;
  4. }

这里可以通过 Idea 快捷键,也可以通过 Project Structure 来设置: 

 

        module2 这里我们同样设置它的模块名为 B ,然后我们引入了 A 模块,但这样并不是说我们就可以访问模块 A 中所有的包了,我们只可以访问模块 A 给我们暴露出来的包!

 测试

  1. package com.lyh;
  2. public class App {
  3. public static void main(String[] args) {
  4. new User("李大国",25).sayHello();
  5. }
  6. }

 运行结果:

 1.2.3、模块的依赖传递

        "依赖传递" 这个名字是我自己起的,意思是说如果我的模块B 引用了模块 A ,而现在我的模块C 引用了模块B ,那么我的模块C 在调用模块B 时,模块B 调用了A 的方法是否可行呢?毕竟模块C 并没有引用模块A 。

        答案是不可行的,但是只需要在模块B 引入模块A 的位置加上一个关键字 transitiive 即可实现依赖的传递,这样之后所有引用了模块B 的模块就不用担心模块B 引用了其它的依赖而自己的模块内部却没有引用而出现问题了:

  1. module B {
  2. // 引入A模块暴露的包并传递依赖
  3. requires transitive A;
  4. }

 1.3、模块内反射访问控制语法

        关于反射,普通程序员一般用的并不多,只有写框架的人用才会经常用,但是这里依然需要了解一下。

上面我们说:默认暴露的包都是允许其它模块通过反射来访问该包下用 public 修饰的内容的。其实,我们还可以通过 opens 语法来定义可以通过反射来访问该包下任意修饰符修饰的内容。

1.3.1、opens  语法

        现在,我们试着通过 opens 语法来让模块C(module3) 来通过反射调用 User 的 sayHello 方法:

 首先修改模块A 的模块描述文件:

  1. module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B,C; // 用 to 指定只能给 B模块使用
  7. // 允许外部模块反射的包,即使是非 public 也是可以访问到的
  8. opens com.lyh.domain to C; // 同样可以指定只能给特定模块访问,多个模块之间用逗号隔开
  9. }

然后创建module3 的模块配置文件,并引入模块A 的包:

  1. module C {
  2. requires A;
  3. }

在 module3 中测试: 

  1. package com.lyh.run;
  2. import java.lang.reflect.Method;
  3. public class App {
  4. public static void main(String[] args) {
  5. try {
  6. Class<?> c = Class.forName("com.lyh.domain.User");
  7. System.out.println(c.getName());
  8. Method sayHello = c.getDeclaredMethod("sayHello");
  9. sayHello.setAccessible(true);
  10. sayHello.invoke(c.getConstructor().newInstance(),null);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }

运行结果:

 1.3.2、open 语法

        除了使用 opens,我们还可以直接在模块名前面直接来一个 open 关键字来定义整个模块暴露的包都允许外部模块通过反射使用:

  1. open module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B,C; // 用 to 指定只能给 B模块使用
  7. }

注意:使用了 open ,我们的模块描述文件中就不能再使用 opens 了,不然会报错。

1.4、模块服务

        问:模块是如何向外界提供服务的?

        答:模块只向外界公开接口,具体的实现类是不公开的,这就实现了模块之间的低耦合,比如我们的模块A有一个接口用来提供数据库服务,模块2需要调用这个接口来获得服务。那么无论模块A中的服务发生怎样的变化,我们的模块B不需要任何改变去适应这种变化。

        下面我们来模拟一下,我们首先在模块A上定义一个接口用来提供数据库服务:

  1. package com.lyh.service;
  2. public interface DBService {
  3. void connect();
  4. }

        然后我们需要提供两个实现类,分别代表连接 MySQL 和 Oracle 数据库的服务:

  1. package com.lyh.impl;
  2. import com.lyh.service.DBService;
  3. public class MySqlServiceImpl implements DBService {
  4. @Override
  5. public void connect() {
  6. System.out.println("连接 MySQL");
  7. }
  8. }
  1. package com.lyh.impl;
  2. import com.lyh.service.DBService;
  3. public class OracleServiceImpl implements DBService {
  4. @Override
  5. public void connect() {
  6. System.out.println("连接 Oracle");
  7. }
  8. }

 然后,我们需要向外部模块提供该服务:通过在 module-info.java 中声明提供的服务:

  1. open module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B,C; // 用 to 指定只能给 B模块使用
  7. exports com.lyh.service;
  8. provides com.lyh.service.DBService with com.lyh.impl.MySqlServiceImpl;
  9. }

        我们首先需要暴露接口所在的包,以便外部模块可以访问;然后我们用 provides 语法来声明提供的服务接口和实现类(注意:这里必须指定接口的实现类)。

现在我们可以在模块B 中去调用模块A 的服务了:
        首先,我们需要引入A 模块并通过 use 语法来使用 A模块提供的服务:

  1. module B {
  2. requires A;
  3. uses com.lyh.service.DBService;
  4. }

测试: 

  1. package com.lyh;
  2. import com.lyh.service.DBService;
  3. import java.util.ServiceLoader;
  4. public class App {
  5. public static void main(String[] args) {
  6. ServiceLoader<DBService> dbServices = ServiceLoader.load(DBService.class);
  7. for(DBService service: dbServices)
  8. service.connect();
  9. }
  10. }

运行结果:

        这样,之后如果我们的模块A 希望修改服务为 Oracle 服务,只需要在 module-info.java 中修改即可:

  1. open module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B,C; // 用 to 指定只能给 B模块使用
  7. exports com.lyh.service;
  8. provides com.lyh.service.DBService with com.lyh.impl.MySqlServiceImpl,com.lyh.impl.OracleServiceImpl;
  9. }

        当然,这里的接口不只可以提供一个服务,如果有多个服务的需求,只需要通过逗号把该接口的实现类分割开来即可: 

  1. open module A {
  2. // java.base 默认就已经导入了,这里可以省略
  3. requires java.base;
  4. // 暴露包给外部模块使用
  5. // 导出的包默认是允许反射访问 public 修饰的包的
  6. exports com.lyh.domain to B,C; // 用 to 指定只能给 B模块使用
  7. exports com.lyh.service;
  8. provides com.lyh.service.DBService with com.lyh.impl.OracleServiceImpl;
  9. }

测试运行:

1.5、模块相关命令行使用

1.5.1、查看 JDK 中所有模块

java --list-modules

1.5.2、查看模块详细信息

java --describe-module java.xml

总结

        至此,模块化的知识点基本上完了,之后在开发过程中遇到什么问题再来更新。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/894583
推荐阅读
相关标签
  

闽ICP备14008679号