Java9 module特性 初识

参考网站:The State of the Module System,Java9模块系统的说明(翻译),Java9 中的 Module, ModulePath 和 ClassPath,Java 9 揭秘(4. 模块依赖)

  • 每个java project中的根目录下都有一个module-info.java
  • 模块名不可重复,因此建议采用类似包名的结构。
  • <>中的名字需要是全限定名,不管是包名、类名或是接口名。
    此外,在exports中,java.langjava.lang.annotation是不同的包,需要分开写,只写java.lang则只能公开该包下的类,而不会公开java.lang.annotation下的类。

使用示例(以及Maven整合java9):https://www.jianshu.com/p/66c26dd3237f


requires public

module  {
	requires ;
	requires public ;
	exports ;
}
  • A模块下可包括packagea、packageb、packagec包,如果要向其他模块公开packagea包,则需要用exports packagea
  • B模块如果要使用A模块,则需要使用requires A(同时还需要将该组件加入到项目->buildpath->Modulepath)
  • 接上,C模块即使引用了B模块requires B,也无法使用A模块中的内容。如果希望让C模块可以通过引用模块B,从而间接引用A模块,则需要在B模块中声明requires public A,这称为隐式可读性(implied readability)。

requires static

module  {
	requires static ;
}

模块系统在编译时以及运行时验证模块的依赖关系。 有时希望在编译时模块依赖性是必需的,但在运行时是可选的。

你在开发一个库时,如果一个特定的模块在运行时可执行更好的库。否则,它将回到另一个模块,使其执行不到最佳的库。但是,库是根据可选模块进行编译的,如果可选模块不可用,则确保不依赖于可选模块的代码执行。

另一个例子是导出注解包的模块。 Java运行时已经忽略不存在的注解类型。 如果程序中使用的注释在运行时不存在,则注解将被忽略。 模块依赖关系在启动时验证,如果模块丢失,应用程序将无法启动。 因此,必须将含有注解包的模块的模块依赖性声明为可选。

以下模块声明包含对com.jdojo.annotation模块的可选依赖关系:

module com.jdojo.claim {
    requires static com.jdojo.annotation;
}

允许在require语句中同时使用transitive 和static 修饰符:

module com.jdojo.claim {
    requires transitive static com.jdojo.annotation;
}

如果transitive 和static 修饰符一起使用,则可以按任何顺序使用。 以下声明具有与之前相同的语义:

module com.jdojo.claim {
    requires static transitive com.jdojo.annotation;
}

exports & exports to

module A {
	exports com.packageA
    exports com.packageB to B;
    exports com.packageC to C,D,E;
}
  • export语句用于将包导出到所有其他模块或某些命名模块。
  • 导出的包中的所有公共类型都可以在编译时和运行时访问。
  • 在运行时,可以使用反射来访问公共类型的公共成员;但公共类型的非公开成员也无法使用反射,即使在这些成员上使用了setAccessible(true)方法。
  • 指定导出的com.packageB仅对B模块可见.
  • 指定导出的com.packageC仅对C,D,E这三个模块可见

open & opens

要反射公共字段,需要exports导出包,这是允许的最小可访问性。要反射非公共字段,必须打开opens该包,这是允许的最大可访问性。

open module com.jdojo.model {
    // Module statements go here
}
  • 在这里,com.jdojo.model模块是一个开放模块。 其他模块可以在本模块中的所有软件包上对所有类型使用深层反射(详见Java 9 揭秘(4. 模块依赖)——四——3. 使用深度反射)(简单来说就是可以通过反射访问非公共字段)。
  • 可以在开放模块中声明 exports, requires, uses, 和provides语句。
  • 但不能在打开的模块中再声明opens语句。 opens语句用于打开特定的包以进行深层反射。 因为开放模块打开所有的软件包进行深层反射,所以在开放模块中不允许再使用open语句。

打开一个包意味着允许其他模块对该包中的类型使用深层反射。 可以打开一个包指定给所有其他模块或特定的模块列表。
打开一个包到所有其他模块的打开语句的语法如下:

opens ;

这里,可用于深入反射所有其他模块。 也可以使用限定的open语句打开包到特定模块:

opens  to , ...;

在这里,仅用于深层反射到,等。以下是在模块声明中使用opens语句的示例:

module com.jdojo.model {
    // Export the com.jdojo.util package to all modules
    exports com.jdojo.util;
    // Open the com.jdojo.util package to all modules
    opens com.jdojo.util;
    // Open the com.jdojo.model.policy package only to the hibernate.core module
    opens com.jdojo.model.policy to hibernate.core;
}
  • com.jdojo.model模块导出com.jdojo.util包,这意味着所有公共类型及其公共成员在编译时可以访问,并在运行时进行反射。
  • 第二个语句在运行时打开相同的包进行深度反射。 总而言之,com.jdojo.util包的所有公共类型及其公共成员都可以在编译时访问,并且该包允许在运行时深层反射。
  • 第三个语句仅将com.jdojo.model.policy包打包到hibernate.core模块进行深层反射,这意味着其他模块在编译时不能访问此包的任何类型,而hibernate.core模块可以访问所有类型及其成员在运行时进行深度反射。

uses & provides to

module com.demo.consumer {
    requires com.example.data;
    uses com.example.data.SortService;
}
  • uses用于声明需要的接口,这样就可以使用ServiceLoader.load方法去加载依赖中的service provider
  • uses [interface]当前模块就会发现它,使java.util.ServiceLoader类进行加载。必须是本模块中的,不能是其他模块中的。其实现类可以由其他模块提供。
module service.sort.bubble {
    requires service.sort;
    provides com.example.data.SortService with sort.impl.bubble.BubbleSort;
}
  • provides [interface] with [implement]声明了是SortService的服务提供方,好让模块系统知道这个模块提供了该接口的实现。
  • 注意这里不需要exports这个实现类,即服务提供模块可以不用exports服务实现。

规范 & 兼容和迁移

java1.8及以前的项目使用java9之后的模块化jar包:

  1. 只需要使用Classpath即可。
  • Classpath中的的Jar包,无论是否模块化都会被当作传统Jar包处理。
  1. 主程序放在classpath中。平台内建模组(jmod)放在modulepath中。
  • 所有Classpath下的内容在Java9中会变成一个未命名模块(Unnamed Module)
  • 未命名模块会导出它的所有包。

java9之后,创建一个模块化的项目,但是依赖的Jar包还没有模块化:

  1. 将非模块化jar包放入modulepath中。
  • 一个不包含module-info.class的传统Jar包,如果放到了ModulePath下,就变成了一个自动模块(Automatic Module)
  • 自动模块默认的依赖于所有Modulepath中的模块,可以访问所有模块中导出的Package。默认导出此模块中的所有Package。
  • 自动模块被授予对其它所有自动模块的隐式可读性。(即自动模块中对其他自动模块的依赖都是requires public [com.automiaticModule],显示模块不需要考虑各个自动模块之间的相互依赖问题)
  • 不同自动模块中的package不能重复。(如果modulepath中两个以上的非模块化jar包包含相同的package,只有一个jar包可以成为自动模块)。

小结

  • Modulepath中的Jar包或Jmod文件被当作Module来处理,而Classpath中的的Jar包,无论是否模块化都会被当作传统Jar包处理。

  • modulepath中模块化jar包为显示模块,非模块化(没有模块声明)的jar包为自动模块;classpath中的jar包被视为未命名模块

    未命名模块

  • Unnamed Module是一个特殊的,自动生成的Module,所有Classpath下的内容在 Java9中都是挂在Unnamed Module(未命名模块)名下的。对于同一个ClassLoader,只有一个 未命名模块。

  • Unnamed Module会导出它的所有包。

  • Unnamed Module可以读取其他任意模块。

  • 显示模块不能读取未命名模块。(这个限制是有意为之的。因为允许命名模块依赖类路径中的任意内容是不可能做到可靠的配置的)

  • 如果一个包被定义在了命名模块和未命名模块中,那么未命名模块中的包会被忽略。(假设com.xxxPackage包是com.moduleA模块的导出包,包里面有Alpha.class类,同时如果一个类路径下的JAR文件包含com/xxxPackage/Alpha.class类,则该文件将永远不会被加载。)

    自动模块

  • 一个不包含module-info.class的传统Jar包,如果放到了ModulePath下,就变成了一个自动模块(Automatic Module)

  • 自动模块默认的依赖于所有Modulepath中的模块,可以访问所有模块中导出的Package。默认导出此模块中的所有Package。

  • 自动模块被授予对其它所有自动模块隐式可读性*(*见总结1)。(自动模块之间)

  • 如果 Jar 包在MetaInfo文件中定义了Automatic-Module-Name,则使用这个值作为模块的名称;如果没有定义,那么使用 Jar 包的文件名去掉扩展名的那部分作为模块名,其中包含的“-”要替换成“.”,因为模块名不允许包含“-”字符。

  • 自动模块可以读取未命名模块。显示模块不能读取未命名模块。(例如,显示模块com.explicitModule中的代码引用了自动模块com.automaticModule中的公共类型,并且自动模块com.automaticModule的方法签名引用了还在Classpath路径中的JAR文件中的类型,那么显示模块com.explicitModule中的代码将无法访问这些类型,因为显示模块不能依赖未命名模块。临时的解决方案为:将显示模块com.explicitModule改为自动模块,直到classpath中的jar包被模块化,并移入modulepath中)

你可能感兴趣的:(java基础)