资讯专栏INFORMATION COLUMN

Java9模块化学习笔记一之快速入门

cjie / 646人阅读

摘要:如果你想查看运行时模块的加载过程输出结果表示为模块,由于我限制了不再往下输出了,而我们模块又没有别的额外依赖,所以仅有这行输出。

jdk9模块快速入门

列出自带模块:java --list-modules
mac多版本jdk共存:http://adolphor.com/blog/2016...
模块规则示意图:

incubator modules:孵化模块 以jdk.incubator开头,比如jdk.incubator.httpclient(jdk11之后这是正式的模块了:[java.net.http][1],具体参考:http://openjdk.java.net/jeps/...

module descriptor:模块描述文件 module-info.java 例如java.prefs的模块描述文件内容:

module java.prefs{
    requires java.xml;
    exports java.util.prefs;
}

requires代表依赖的模块,只有依赖的模块存在才能通过编译并运行.需要注意的是,所有模块均自动隐式依赖java.base模块,不需要显示声明
exports指出需要暴露的包,如果某个包没有被exports,那么其他模块是无法访问的。

两个名词:Readability,Accessibility

Readability:指的是必须exports的包才可被其他模块访问
Accessibility:指的是即使是exports的包,其中的类的可访问下也要基于java的访问修饰符,仅有public修饰的才可被其他模块访问

Implied Readability(隐式Readability, requires transitive):
Readability默认情况下是不会被传递的, 比如Maven中,我们知道依赖可以被传递,但是module的requires不会被传递,比如: 下图中java.desktop无法访问java.xml模块中exports的包,虽然它引用了java.prefs模块

但是如果你将requires改成requires transitive的话,那么传递性依赖就可以生效了。
有个时候我们定义API模块时经常忘记哪些该requires transitive,这时该怎么办呢? 模块编译时使用-Xlint:exports选项,它会检测出这些问题并warn。
Aggregate Module(聚合模块)
由于requires transitive的存在,就可以支持聚合模块。有些聚合模块可以没有任何代码,就一个module-info.java描述文件,比如java.se, java.se.ee模块
不建议直接引用java.se模块,因为它就相当于java9以前版本的rt.jar的内容。

Qualified Exports(有限制的exports)
比如我只想exports某个包给部分模块,而不是所有模块

exports com.sun.xml.internal.stream.writers to java.xml.ws,java.sql;

Qualified Exports不建议普通模块使用,java platform使用它主要是为了减少内部重复代码,一般用它暴露internal内部使用的一些类给部分模块。

module path
module path类似于classpath,只不过它只包括module
module resolution支持定义模块依赖图,根据root module去查询。

在modular jdk中以非模块化方式开发
在java9中也是允许你以非模块化方式开发和运行应用的(也就是说,模块化开发是可选的),如果你的应用中没有module-info.java,那么这就是一个unnamed module. java9对于unnamed module的处理方式就是所有的jdk模块均直接可用(模块图中是以java.se模块作为root模块的,也意味着多带带处于java.se.ee下的一些包,比如JAXB API是无法访问到的)。
但是需要注意的是,在java8以及之前的版本中,我们可以访问jdk中的一些不推荐访问的内部类,比如com.sun.image.codec.jpeg,但在java9模块化之后被强封装了,所以在java9中无法使用这些内部类,也就是说无法通过编译,但是java9为了保持兼容性,允许之前引用这些内部类的已有的jar或已编译的类正确运行。换言之,就是java9不允许源码中引用这些类,无法通过编译,但是之前版本中引用这些类的已编译class文件是允许正常运行的。

模块结构
MacBook-Pro:easytext-singlemodule tjw$ tree
.
├── README.md
├── out
│   └── easytext
│       ├── javamodularity
│       │   └── easytext
│       │       └── Main.class
│       └── module-info.class
├── run.sh
└── src
    └── easytext
        ├── javamodularity
        │   └── easytext
        │       └── Main.java
        └── module-info.java

其中区别于传统src目录,模块src目录下首先是模块目录easytext,这名字与module-info.java里的保持一致,模块目录下包含源码及module-info.java模块描述文件。src/easytext是源码目录, javamodularity/easytext是包

编译文件run.sh内容:

mkdir -p out

javac -d out --module-source-path src -m easytext
java --module-path out -m easytext/javamodularity.easytext.Main

打包成jar

jar -cfe out/easytext.jar javamodularity.easytext.Main -C out/easytext .

-cf不用说了,-e指定入口类,-C指定需要打包进jar的文件路径
运行模块jar:

java --module-path out -m easytext

请注意,这与直接运行模块目录的区别:java --module-path out -m easytext/javamodularity.easytext.Main, 运行模块jar由于我们打包时通过-e选项指定了入口类,这时-m只需要指定模块名即可运行。

如果你想查看运行时模块的加载过程:java --show-module-resolution --limit-modules java.base --module-path out -m easytext
输出结果: 表示easytext为root模块,由于我限制了java.base不再往下输出了,而我们模块又没有别的额外依赖,所以仅有这行输出。

root easytext file:///Users/tjw/learn/java-9-moodularity-examples/chapter3/easytext-singlemodule/out/easytext.jar

假如我去掉--limit-modules限制,运行,则输出如下:java --show-module-resolution --module-path out -m easytext

root easytext file:///Users/tjw/learn/java-9-moodularity-examples/chapter3/easytext-singlemodule/out/easytext.jar
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds jdk.charsets jrt:/jdk.charsets
java.base binds jdk.jlink jrt:/jdk.jlink
java.base binds jdk.jartool jrt:/jdk.jartool
java.base binds jdk.jdeps jrt:/jdk.jdeps
java.base binds jdk.compiler jrt:/jdk.compiler
java.base binds jdk.javadoc jrt:/jdk.javadoc
java.base binds jdk.packager jrt:/jdk.packager
java.base binds java.desktop jrt:/java.desktop
java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki
java.base binds java.naming jrt:/java.naming
java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec
java.base binds java.xml.crypto jrt:/java.xml.crypto
java.base binds java.security.jgss jrt:/java.security.jgss
java.base binds java.security.sasl jrt:/java.security.sasl
java.base binds jdk.deploy jrt:/jdk.deploy
java.base binds java.smartcardio jrt:/java.smartcardio
java.base binds jdk.security.jgss jrt:/jdk.security.jgss
java.base binds java.logging jrt:/java.logging
java.base binds jdk.security.auth jrt:/jdk.security.auth
java.base binds java.management jrt:/java.management
java.base binds jdk.zipfs jrt:/jdk.zipfs
jdk.security.auth requires java.security.jgss jrt:/java.security.jgss
jdk.security.auth requires java.naming jrt:/java.naming
jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss
jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl
jdk.security.jgss requires java.logging jrt:/java.logging
jdk.deploy requires jdk.unsupported jrt:/jdk.unsupported
jdk.deploy requires java.desktop jrt:/java.desktop
jdk.deploy requires java.naming jrt:/java.naming
jdk.deploy requires java.scripting jrt:/java.scripting
jdk.deploy requires java.prefs jrt:/java.prefs
jdk.deploy requires java.logging jrt:/java.logging
jdk.deploy requires java.rmi jrt:/java.rmi
jdk.deploy requires java.xml jrt:/java.xml
jdk.deploy requires java.management jrt:/java.management
java.security.sasl requires java.logging jrt:/java.logging
java.security.jgss requires java.naming jrt:/java.naming
java.xml.crypto requires java.logging jrt:/java.logging
java.xml.crypto requires java.xml jrt:/java.xml
java.naming requires java.security.sasl jrt:/java.security.sasl
jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec
java.desktop requires java.datatransfer jrt:/java.datatransfer
java.desktop requires java.xml jrt:/java.xml
java.desktop requires java.prefs jrt:/java.prefs
jdk.packager requires java.logging jrt:/java.logging
jdk.packager requires java.desktop jrt:/java.desktop
jdk.packager requires java.xml jrt:/java.xml
jdk.packager requires jdk.jlink jrt:/jdk.jlink
jdk.javadoc requires jdk.compiler jrt:/jdk.compiler
jdk.javadoc requires java.xml jrt:/java.xml
jdk.javadoc requires java.compiler jrt:/java.compiler
jdk.compiler requires java.compiler jrt:/java.compiler
jdk.jdeps requires jdk.compiler jrt:/jdk.compiler
jdk.jdeps requires java.compiler jrt:/java.compiler
jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt
jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps
java.prefs requires java.xml jrt:/java.xml
java.rmi requires java.logging jrt:/java.logging
java.management binds java.management.rmi jrt:/java.management.rmi
java.management binds jdk.management.cmm jrt:/jdk.management.cmm
java.management binds jdk.management.jfr jrt:/jdk.management.jfr
java.management binds jdk.management jrt:/jdk.management
java.management binds jdk.internal.vm.compiler.management jrt:/jdk.internal.vm.compiler.management
java.scripting binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
java.naming binds jdk.naming.dns jrt:/jdk.naming.dns
java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi
java.datatransfer binds java.desktop jrt:/java.desktop
java.compiler binds jdk.compiler jrt:/jdk.compiler
java.compiler binds jdk.javadoc jrt:/jdk.javadoc
jdk.naming.rmi requires java.rmi jrt:/java.rmi
jdk.naming.rmi requires java.naming jrt:/java.naming
jdk.naming.dns requires java.naming jrt:/java.naming
jdk.scripting.nashorn requires java.logging jrt:/java.logging
jdk.scripting.nashorn requires java.scripting jrt:/java.scripting
jdk.scripting.nashorn requires jdk.dynalink jrt:/jdk.dynalink
jdk.internal.vm.compiler.management requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci
jdk.internal.vm.compiler.management requires jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler
jdk.internal.vm.compiler.management requires jdk.management jrt:/jdk.management
jdk.internal.vm.compiler.management requires java.management jrt:/java.management
jdk.management requires java.management jrt:/java.management
jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr
jdk.management.jfr requires java.management jrt:/java.management
jdk.management.jfr requires jdk.management jrt:/jdk.management
jdk.management.cmm requires jdk.management jrt:/jdk.management
jdk.management.cmm requires java.management jrt:/java.management
java.management.rmi requires java.management jrt:/java.management
java.management.rmi requires java.naming jrt:/java.naming
java.management.rmi requires java.rmi jrt:/java.rmi
jdk.dynalink requires java.logging jrt:/java.logging
jdk.internal.vm.compiler requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci
jdk.internal.vm.compiler requires jdk.unsupported jrt:/jdk.unsupported
jdk.internal.vm.compiler requires java.instrument jrt:/java.instrument
jdk.internal.vm.compiler requires jdk.management jrt:/jdk.management
jdk.internal.vm.compiler requires java.management jrt:/java.management
jdk.internal.vm.ci binds jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler
jdk.dynalink binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn

模块路径
模块路径格式支持三种:大目录下面有多个模块的情形,比如上述的out目录;模块目录本身;模块jar
模块路径分隔符,遵循系统的Path.sep;比如mac/Linux下是 : ,windows下是 ;
--module-path 可以简写为 -p 如 java -p

Linking Modules

jlink工具允许你创建一个运行时镜像runtime image,包含java应用允许所需的最小集合。而不是像之前那样需要打包整个jre.

    $JAVA_HOME/bin/jlink --module-path out/:$JAVA_HOME/jmods 
        --add-modules easytext 
        --launcher easy=easytext 
        --output easytext-image

需要注意的是,默认情况下jlink没有被添加到PATH中,需要你手动添加一下

跟javac和java命令不同,jlink需要指定jdk平台的模块路径$JAVA_HOME/jmods
--add-modules 指定easytext为root模块
--launcher指定启动的入口为easytext模块,easy是生成的启动文件名
--output 指定生成的镜像路径

最终生成的镜像文件内容:

easytext-image/
├── bin/
│   ├── easy
│   ├── java
│   └── keytool
├── conf
│   └── security
│       └── policy
│           ├── limited
│           └── unlimited
├── include
│   └── darwin
├── legal
│   └── java.base
└── lib
    ├── jli
    ├── security
    └── server

试了下,打包成压缩文件后,只有12M大小:

-rw-r--r--  1 tjw  staff    12M  6 29 13:32 easy.tar.gz
查看已有模块描述

查看已有模块描述有两种方式:
一、直接看module-info.java
二、使用命令 java --describe-module javafx.controls

模块对于GUI应用的问题:

注意:Jdk11已经将javafx从平台模块中移除

module easytext.gui {

   exports javamodularity.easytext.gui to javafx.graphics;

   requires javafx.graphics;
   requires javafx.controls;
   requires easytext.analysis;
}

exports javamodularity.easytext.gui to javafx.graphics;是必须的,因为gui应用中Application会反射访问具体的实现,那么这就意味着javafx.graphics.Application的运行时需要能够访问我们的应用模块的Application实现。

思考一个问题:如何确保模块之间的解耦,通过定义一个接口模块可以分离实现,但是如何确保自动加载哪个实现呢?

Services

services的设计有点类似于ioc,概念上分为服务提供者和服务消费者。
主要入口类就是java.util.ServiceLoader,这个类在jdk6的时候就已经存在,不过在jdk9进行了改造以支持模块化,jdk9之前ServiceLoader主要是用来使jdk更加插件化,一些框架比如dubbo也会使用ServiceLoader来做插件化工作。 jdk9之前services的提供是在jar包下的META-INF/services目录下的一个文本文件,文件名为服务接口的全限定类名,如:com.test.HelloWorld,文件内容也为服务实现的全限定类名com.test.HelloWorldImpl。比如dubbo的filter

jdk9改造后使得ServiceLoader支持模块化service加载,已达到模块间面向接口,使实现解耦的目的。注意:服务提供模块可以不用exports服务实现。
步骤:
0、服务接口模块定义
如模块easytext.analysis.api
1、提供模块描述中使用provides

module easytext.analysis.coleman {
   requires easytext.analysis.api;

   provides javamodularity.easytext.analysis.api.Analyzer with javamodularity.easytext.analysis.coleman.Coleman;
}

2、消费模块描述中使用uses

module easytext.cli {
   requires easytext.analysis.api;

   uses javamodularity.easytext.analysis.api.Analyzer;
}

3、消费模块代码中使用ServiceLoader类加载

      Iterable analyzers = ServiceLoader.load(Analyzer.class); //其实ServiceLoader.load返回的是一个ServiceLoader实例,只不过它实现了Iterable接口

      for (Analyzer analyzer: analyzers) { 
        System.out.println(analyzer.getName() + ": " + analyzer.analyze(sentences));
      }
Services生命周期

ServiceLoader.load是懒加载的
ServiceLoader.load每调用一次都会返回一个ServiceLoader实例,获取的服务实例也是新的,跟Spring等容器不同,它不存在单例模式。这就需要注意,千万不要通过服务实例来共享状态。
服务实现类要么提供无参构造器,要么提供public static provider()方法,返回实例。

public static ExampleProviderMethod provider() {
    return new ExampleProviderMethod("Analyzer created by static method");
}
结合java8接口静态方法实现service工厂模式:
public interface Analyzer {

   String getName();

   double analyze(List> text);

   static Iterable getAnalyzers() {
     return ServiceLoader.load(Analyzer.class); // <1>
   }

}
module easytext.analysis.api {
   exports javamodularity.easytext.analysis.api;

   uses javamodularity.easytext.analysis.api.Analyzer;
}

好处:ServiceLoader的调用及模块uses声明都统一在api模块中定义。

Service Type及延迟初始化再谈

问题: 上面所述的获取服务接口实现的方式只能遍历Itreable,而遍历后所有的实现都会被初始化。两个问题:1、我只关心某一个实现,如何标识获取;2、我只想获取某一个特定实现,但我不想在遍历中初始化其他实例;
问题1:我只关心某一个实现,如何标识获取
方案1:在服务接口中添加标识方法,比如getName,然后消费者遍历时通过getName的值来判断。这样很多场景下是可行的。但是也有些场景需要通过别的方式来标识,比如是否实现某个抽象类,是否被某个注解标注,这种情况下就需要下面的方案2。
方案2: Java9对ServiceLoader API进行了强化,提供ServiceLoader.Provider stream. ServiceLoader.Provider可以在不实例化实现之前对实现类进行反射检索。
比如下面的就是通过检索被@Fast注解标注的实现。

public class Main {
  public static void main(String args[]) {
    ServiceLoader analyzers =
      ServiceLoader.load(Analyzer.class);

    analyzers.stream()
      .filter(provider -> isFast(provider.type()))
      .map(ServiceLoader.Provider::get) 
      .forEach(analyzer -> System.out.println(analyzer.getName()));
  }

  private static boolean isFast(Class clazz) {
    return clazz.isAnnotationPresent(Fast.class)
      && clazz.getAnnotation(Fast.class).value() == true;

  }
}

注意上述代码中的provider.type(),它返回的是实现类的Class对象,这里我们要注意,我们的实现类是没有被exports的,但通过provider.type()是可以获取到。但是我们可以调用provider.type().newInstance()吗?不可以,因为它仍然遵守模块的封装约定。如果强行调用会报IllegalAccessError异常。

Service Moudle Resolution And Linking

对于Service,模块解析方式和之前的相同:从root模块开始,解析requires,然后解析uses,然后会把uses对应的provides模块都解析到resolved module sets中。
**但是对于jlink镜像打包而言,它不会把Service的provides模块打包进去(一个直接的原因就是java.base中使用了大量的uses),
所以使用jlink打包时需要注意通过--add-modules添加provides。** 当然如果你不知道有哪些provids模块,可以通过jlink选项 --suggest-providers 接口名查看

$JAVA_HOME/bin/jlink --module-path mods/:$JAVA_HOME/jmods --add-modules main--suggest-providers javamodularity.easytext.analysis.api.Analyzer

建议的提供方:
  provider.factory.example provides javamodularity.easytext.analysis.api.Analyzer used by main
  provider.method.example provides javamodularity.easytext.analysis.api.Analyzer used by main

不过如果provide模块也uses别的模块,那么也需要照样分析,并根据需要--add-modules添加进来。

不过还有个替代方式,添加--bind-services选项,添加后jlink会解析uses及provides,但不推荐使用,因为java.base也使用了大量uses,会导致打包后镜像很大

源码见书籍源码:https://github.com/java9-modu...

主要参考书籍:《Java 9 Modularity Patterns and Practices for Developing Maintainable Applications》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71416.html

相关文章

  • Java9块化学习笔记三之迁移到Java9

    摘要:命令行参数文件鉴于迁移到后可能需要很长的命令行参数,有些会限制命令行长度,支持定义一个命令行参数文件。已有三分库可以自动转成模块,只要在启动时将放在指定路径中,便会自动变成。 java[c]命令行参数文件 鉴于迁移到java9后可能需要很长的命令行参数,有些os会限制命令行长度,java9支持定义一个命令行参数文件。使用方式: java @arguments.txt arguments...

    NeverSayNever 评论0 收藏0
  • Java9块化学习笔记二之模块设计模式

    摘要:但是模块化当中,无法扫描只有模块中可以使用有两种解决方案定义一个专门的资源模块,并使用提供的接口,实现它,并将这个实现注册为服务。有两种方式使用或包名,包名模块名使用运行时动态。 模块设计的原则: 1、防止出现编译时循环依赖(主要是编译器不支持),但运行时是允许循环依赖的,比如GUI应用2、明确模块的边界 几种模块设计: API模块,聚合模块(比如java.base) 可选依赖 两种方...

    李文鹏 评论0 收藏0
  • Python爬虫学习路线

    摘要:以下这些项目,你拿来学习学习练练手。当你每个步骤都能做到很优秀的时候,你应该考虑如何组合这四个步骤,使你的爬虫达到效率最高,也就是所谓的爬虫策略问题,爬虫策略学习不是一朝一夕的事情,建议多看看一些比较优秀的爬虫的设计方案,比如说。 (一)如何学习Python 学习Python大致可以分为以下几个阶段: 1.刚上手的时候肯定是先过一遍Python最基本的知识,比如说:变量、数据结构、语法...

    liaoyg8023 评论0 收藏0
  • Java9特性预览Jigsaw:块化系统快速入门指南

    摘要:例子中的文件路径使用斜杠,路径分隔符是冒号。到目前为止的示例中,已编译的模块的内容在文件系统上以分散的文件的形式存储。当用于分发和部署时通常更方便的方式是将一个模块打包成一个模块化的。在这个例子中,模块打包时表明了它的版本是。 本文档提供了几个简单的例子,让开发人员开始使用模块。 例子中的文件路径使用斜杠,路径分隔符是冒号。使用微软Windows开发的人员应该使用文件路径以反斜杠和一个...

    dack 评论0 收藏0
  • 零基础如何学爬虫技术

    摘要:楚江数据是专业的互联网数据技术服务,现整理出零基础如何学爬虫技术以供学习,。本文来源知乎作者路人甲链接楚江数据提供网站数据采集和爬虫软件定制开发服务,服务范围涵盖社交网络电子商务分类信息学术研究等。 楚江数据是专业的互联网数据技术服务,现整理出零基础如何学爬虫技术以供学习,http://www.chujiangdata.com。 第一:Python爬虫学习系列教程(来源于某博主:htt...

    KunMinX 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<