Java 9 中的模块Module系统

 更新时间:2022年03月24日 16:33:19   作者:码农小胖哥  
Java 9 引入的模块是在Java包(package)的基础上又引入的一个新的抽象层,基于package这一点很重要,这里需要强调一下,接下来通过本文给大家介绍Java 9 中的模块Module系统,感兴趣的朋友一起看看吧

Java 的模块在Java 9中正式实装,一直没时间来研究一下这个东西,今天就和大家一起学习一下这个功能。

Java模块解决了什么问题

最近很多同学问我,胖哥,该怎么学习?该学习什么?这里胖哥也穿插说一下。不管学东西,一定要先搞清楚学了有什么用,是学了马上就能用上还是以后有用。我觉得在时间有限的情况下,一定要学当前立马有用的东西。接下来咱们就一起来看看Java模块到底有啥用。

我觉得模块化最大的意义就是按照功能把代码逻辑分割开来,就像你干前端,我写后端,他做测试,把整体大概念拆成小概念,用的时候自由组合,按需引用。事实上确实有这方面的作用,但是不仅仅就这么多。

简化类库

JDK类库目前太臃肿了,在一些微型设备上可能用不到全部的功能,在目前的情况下却不得不引用全部的类库。Java 9引入模块功能后,JDK、JRE、甚至是JAR都可以把用不到的类库排除掉,大大降低了依赖库的规模。

真正的访问隔离

在之前只要类是public的,可以直接在整个依赖可传递的范围内访问它。但是很多时候我们需要在某个范围去限制一些类的访问,让这些类具有一些封闭性。在引入模块后,我们就可以做到这些,把一些我们不想暴露的内部实现细节安全地隐藏起来。

什么是模块?

Java 9 引入的模块是在Java包(package)的基础上又引入的一个新的抽象层。基于package这一点很重要,这里需要强调一下。

模块的结构

Java 模块可以由一个或者多个在一起的 Java 包组成。结构可以参考这个图:

创建模块

创建模块需要如下几个步骤:

  • 创建一个文件夹,通常是一个包名,例如cn.felord.module
  • 然后在cn.felord.module下创建一个module-info.java文件,这个文件被称为模块描述符文件。
  • 在模块描述符文件的同级别创建Java包。
  • 最后在创建的包下编写你的Java类文件即可。

创建模块规则

创建模块也必须遵守下面的规则:

模块名称必须是唯一的。

模块描述符文件module-info.java 必须有。

包名称必须是唯一的。即使在不同的模块中,我们也不能有相同的包名。

每个模块将创建一个 jar 文件。对于多个 jar,我们需要创建单独的模块。

一个项目可以由多个模块组成。

模块类型

模块同样还具有类型,一共有四种。

系统模块

来自 JDK 和 JRE 的模块。可以使用 java --list-modules 列出,这里列出了一部分:

❯ .\java.exe --list-modules
java.base@17
java.compiler@17
java.datatransfer@17
java.desktop@17
java.instrument@17
java.logging@17
java.management@17
java.management.rmi@17
# 省略……

应用程序模块

在应用程序中创建以实现功能的所有模块,日常开发如果涉及到模块应该属于这一类。

自动模块

现有的 jar 文件,感觉像兼容旧的类库。它们其实不是模块。当我们将非模块 jar 添加到模块路径时,会创建具有 jar 名称的模块。该模块有以下特性:

  • 默认导出所有包。
  • 默认情况下可以访问所有其他模块的类。

未命名模块

添加到类路径中的 jar 和类。当我们将 jar 或类添加到类路径时,所有这些类都会添加到未命名的模块中

  • 只导出到其他未命名的模块和自动模块。这意味着,应用程序模块无法访问这些类。
  • 它可以访问所有模块的类。

模块描述符文件

一个模块只有一个module-info.java,而且它是有格式要求的,我们来了解一下。

声明模块

我们只需要在module-info.java这样做就能声明一个名称为cn.felord的模块:

module cn.felord {
}

模块名称应该是两个单词以上,并用英文句号.隔开,上面是一个空模块。

导出包

默认情况下,模块里下所有包都是私有的,即使被外部依赖也无法访问,一个模块之内的包还遵循之前的规则不受模块影响。我们可以使用 export 关键字公开特定的包,就像这样:

module cn.felord {
    exports cn.felord.pkg;
    exports cn.felord.util;
}

请注意cn.felord.pkgexports cn.felord.util不能是空包,导出的包必须声明Java对象。

不能导出具体的Java类。

定向导出包

还有一种是定向导出,该包仅仅向某模块公开。就像什么特供酒、特供烟一样。它的语法是:

exports <包名> to <目标模块1>,<目标模块2>,<目标模块3>,...

我们把上面的cn.felord.util定向导出给com.xxx:

module cn.felord {
    exports cn.felord.pkg to com.xxx,com.ooo;
    exports cn.felord.util to com.xxx;
}

在上述情况下,所有模块都可以访问 cn.felord.pkg,但只有com.xxx模块能访问 cn.felord.util

定向导包的作用域是模块。

依赖

如果一个模块要访问从其它模块导出的包,则该模块必须使用requires关键字导入要访问的包所在的模块。就像上面,虽然cn.felord模块向com.ooo开放了cn.felord.pkg包, 即使com.ooo依赖了cn.felord也不能直接使用该包下面的类,需要这样做:

module com.ooo {
    exports com.ooo.pkg;
    // 注释掉 Pkg就变红了 cn.felord.util下面的类无法使用
    requires cn.felord;
}

requires的作用域是模块。

静态依赖

有时我们只在编译时需要一些模块,它们在运行时是可选的。例如,测试或代码生成库。这就需要用到静态导入了,关键字是requires static,例如:

module com.xxx {
       // 移除pom 依赖编译不了
    requires static cn.felord;
}

在此示例中,cn.felord 在编译时是必需的,但在运行时是可选的,有点类似Maven中的<scope>compile</scope>

依赖传递

这看起来越来越像Maven了!a模块依赖b模块,b模块依赖c模块,如果a模块想用c模块公开的包的话,按照前面的规则需要再requires模块c。现在借助于requires transitive就可以这样干,因为b承上启下,我们可以这样:

module b {
    exports b.pkg;
    // 开启依赖传递
    requires transitive  c; 
}

module c {
   exports c.pkg
module a {
   requires b; 

所有依赖b的模块将自动依赖c导出的包,export to定向导出的包优先级最高。

使用服务

使用 uses 关键字,我们可以指定我们的模块需要或使用某些服务。这个服务通常是一个接口或抽象类。它不应该是一个实现类。

module com.xxx {
    requires com.ooo;
    // 移除pom 依赖编译不了
    requires static cn.felord;
    uses com.ooo.pkg.Read;
}

uses只能从模块自己的包中或者requiresrequires static以及requires transitive传递过来的接口或者抽象类。

uses用于指定所需要的服务类或者接口。

给予服务

我们可以通过 provides ... with ...语法,在模块中声明一些服务的实现供其它模块(通过uses)使用。

开放反射

反射 API 的 Java 9 封装和安全性得到了改进。使用反射,我们甚至可以访问对象的私有成员。

从 java 9 开始,默认情况下不打开。我们可以明确地通过open授予其它模块反射权限。

open com.xxx{
}

在这种情况下,com.xxx 模块的所有包都可以使用反射访问。

opens

我们不想全部开放反射访问的话还可以使用opens关键字来指定反射可以访问的包:

module com.xxx{
  opens com.xxx.reflect;
}

opens … to

当然我们还可以将特定的包开放给指定的模块来反射访问:

module com.xxx{
  opens com.xxx.reflect to com.ooo;
}

com.xxx模块的com.xxx.reflect包将开放给com.ooo模块来反射访问。

总结

模块的东西主要是理解,实际运用主要用来系统瘦身、依赖jar级别的隔离。这个自己用Java 9 以上版本建一个多模块的Maven或者Gradle项目,按照上面实验一下就明白了。

到此这篇关于Java 9 的模块(Module)系统的文章就介绍到这了,更多相关Java 9 模块系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用SpringBoot项目导入openfeign版本的问题

    使用SpringBoot项目导入openfeign版本的问题

    这篇文章主要介绍了使用SpringBoot项目导入openfeign版本的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Springboot自动装配之注入DispatcherServlet的实现方法

    Springboot自动装配之注入DispatcherServlet的实现方法

    这篇文章主要介绍了Springboot自动装配之注入DispatcherServlet,Springboot向外界提供web服务,底层依赖了springframework中的web模块来实现,那么springboot在什么时机向容器注入DispatcherServlet这个核心类的呢?带着这个问题一起通过本文学习吧
    2022-05-05
  • 完美解决java.lang.OutOfMemoryError处理错误的问题

    完美解决java.lang.OutOfMemoryError处理错误的问题

    下面小编就为大家带来一篇完美解决java.lang.OutOfMemoryError处理错误的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • springboot读取application.yaml文件数据的方法

    springboot读取application.yaml文件数据的方法

    这篇文章主要为大家详细介绍了springboot读取application.yaml文件数据的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • 2021年最新Redis面试题汇总(1)

    2021年最新Redis面试题汇总(1)

    在程序员面试过程中redis相关的知识是常被问到的话题。这篇文章主要介绍了几道Redis面试题,整理一下分享给大家,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 聊一聊new对象与Spring对bean的初始化的差别

    聊一聊new对象与Spring对bean的初始化的差别

    这篇文章主要介绍了聊一聊new对象与Spring对bean的初始化的差别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • JAVA抛出异常的三种形式详解

    JAVA抛出异常的三种形式详解

    这篇文章主要介绍了JAVA抛出异常的三种形式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 解决SpringBoot项目启动后网页显示Please sign in的问题

    解决SpringBoot项目启动后网页显示Please sign in的问题

    这篇文章主要介绍了解决SpringBoot项目启动后网页显示Please sign in的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • mybatis打印SQL,并显示参数的实例

    mybatis打印SQL,并显示参数的实例

    这篇文章主要介绍了mybatis打印SQL,并显示参数的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • MyBatis-Plus如何通过注解使用TypeHandler

    MyBatis-Plus如何通过注解使用TypeHandler

    这篇文章主要介绍了MyBatis-Plus如何通过注解使用TypeHandler,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论