本文档提供了几个简单的例子,让开发人员开始使用模块。java
例子中的文件路径使用斜杠,路径分隔符是冒号。使用微软Windows开发的人员应该使用文件路径以反斜杠和一个分号做为路径分隔符。shell
第一个例子是一个只打印“Greetings!”命名为com.greetings的模块。该模块包括两个源文件:模块声明文件(module-info.java)和Main.javasegmentfault
按惯例,模块的源代码在一个目录中,该目录是模块的名称:网络
src/com.greetings/com/greetings/Main.java src/com.greetings/module-info.java $ cat src/com.greetings/module-info.java module com.greetings { } $ cat src/com.greetings/com/greetings/Main.java package com.greetings; public class Main { public static void main(String[] args) { System.out.println("Greetings!"); } }
使用如下命令,将源码编译到 mods/com.greetings 目录下:app
$ mkdir -p mods/com.greetings $ javac -d mods/com.greetings src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
如今咱们用下面的命令运行这个例子:socket
$ java -modulepath mods -m com.greetings/com.greetings.Main
-modulepath是模块的路径,它的值是一个或多个包含模块的目录。-m 选项指定了主模块,在/后的值是模块主类包含包名的完整名称。ide
第二个示例是在以前示例的基础上增长了org.astro模块依赖,模块org.astro提供org.astro包的API。模块化
src/org.astro/module-info.java src/org.astro/org/astro/World.java src/com.greetings/com/greetings/Main.java src/com.greetings/module-info.java $ cat src/org.astro/module-info.java module org.astro { exports org.astro; } $ cat src/org.astro/org/astro/World.java package org.astro; public class World { public static String name() { return "world"; } } $ cat src/com.greetings/module-info.java module com.greetings { requires org.astro; } $ cat src/com.greetings/com/greetings/Main.java package com.greetings; import org.astro.World; public class Main { public static void main(String[] args) { System.out.format("Greetings %s!%n", World.name()); } }
该模块编译,在同一时间。使用javac命令指定模块路径编译模块com.greetings,并有模块org.astro的引用,这样能够获取到org.astro提供的API。工具
$ mkdir mods/org.astro mods/com.greetings $ javac -d mods/org.astro src/org.astro/module-info.java src/org.astro/org/astro/World.java $ javac -modulepath mods -d mods/com.greetings src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
该示例以与第一个示例彻底相同的方式运行:测试
$ java -modulepath mods -m com.greetings/com.greetings.Main Greetings world!
在前面的示例,模块 com.greetings 和模块 org.astro 是分别编译的。使用javac 命令编译模块时还能够一次编译多个模块。
$ mkdir mods $ javac -d mods -modulesourcepath src $(find src -name "*.java") $ find mods -type f mods/com.greetings/com/greetings/Main.class mods/com.greetings/module-info.class mods/org.astro/module-info.class mods/org.astro/org/astro/World.class
到目前为止的示例中,已编译的模块的内容在文件系统上以分散的文件的形式存储。当用于分发和部署时一般更方便的方式是将一个模块打包成一个模块化的JAR。一个模块化的JAR是一个普通的jar文件中在顶级目录有一个module-info.class。下面的示例在mlib目录org.astro@1.0.jar和com.greetings.jar。
$ mkdir mlib $ jar --create --file=mlib/org.astro@1.0.jar --module-version=1.0 -C mods/org.astro . $ jar --create --file=mlib/com.greetings.jar --main-class=com.greetings.Main -C mods/com.greetings . $ ls mlib com.greetings.jar org.astro@1.0.jar
在这个例子中,org.astro模块打包时代表了它的版本是1.0 (--module-version=1.0
)。模块com.greetings在打包时代表其主类是com.greetings.Main (--main-class=com.greetings.Main
)。咱们如今能够执行模块com.greetings而无需指定其主类:
$ java -mp mlib -m com.greetings Greetings world!
上面的命令中使用了 -modulepath
的简写 -mp
。
jar工具增长了不少新的选项(能够经过jar -help
查看),其中之一是打印一个模块jar的模块声明:
原文中给出的是:
$ jar --print-module-descriptor --file=mlib/org.astro@1.0.jar Name: org.astro@1.0 Requires: java.base [ MANDATED ] Exports: org.astro
但我实际在Windows下测试的结果为:
C:\Users\coderknock\workspace\JDK9>jar --print-module-descriptor --file=mlib\org.astro@1.0.jar org.astro@1.0 requires mandated java.base exports org.astro C:\Users\coderknock\workspace\JDK9>jar --print-module-descriptor --file=mlib\com.greetings.jar com.greetings requires mandated java.base requires org.astro conceals com.greetings main-class com.greetings.Main
若是咱们在上一个示例中在com.greetings module的模块声明中没有设定依赖org.astro,让咱们看看会发生什么:
$ cat src/com.greetings/module-info.java module com.greetings { // requires org.astro; } $ javac -modulepath mods -d mods/com.greetings src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java src/com.greetings/com/greetings/Main.java:2: 错误: 程序包org.astro不存在 import org.astro.World; ^ src/com.greetings/com/greetings/Main.java:5: 错误: 找不到符号 System.out.format("Greetings %s!%n", World.name()); ^ 符号: 变量 World 位置: 类 Main 2 个错误
如今咱们试一下,com.greetings声明了依赖,可是org.astro没有声明输出会发生什么:
$ cat src/com.greetings/module-info.java module com.greetings { requires org.astro; } $ cat src/org.astro/module-info.java module org.astro { // exports org.astro; } $ javac -modulepath mods -d mods/com.greetings src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java src/com.greetings/com/greetings/Main.java:2: 错误: 程序包org.astro不存在 import org.astro.World; ^ src/com.greetings/com/greetings/Main.java:5: 错误: 找不到符号 System.out.format("Greetings %s!%n", World.name()); ^ 符号: 变量 World 位置: 类 Main 2 个错误
服务容许服务消费者模块和服务提供商模块之间的松散耦合。
这个例子有一个服务消费模块和一个服务提供模块:
模块com.socket输出了一个网络套接字的API。API被封装在com.socket包,因此这个包是输出者。API是可插拔的,容许替换具体的实现。com.socket.spi.networksocketprovider是实际提供服务的抽象类,因此com.socket.spi也应该被输出。
org.fastsocket模块是一个服务提供模块,它提供了一个com.socket.spi.NetworkSocketProvider的实现,不须要输出。
下面是 com.socket 模块的源码:
$ cat src/com.socket/module-info.java module com.socket { exports com.socket; exports com.socket.spi; uses com.socket.spi.NetworkSocketProvider; } $ cat src/com.socket/com/socket/NetworkSocket.java package com.socket; import java.io.Closeable; import java.util.Iterator; import java.util.ServiceLoader; import com.socket.spi.NetworkSocketProvider; public abstract class NetworkSocket implements Closeable { protected NetworkSocket() { } public static NetworkSocket open() { ServiceLoader<NetworkSocketProvider> sl = ServiceLoader.load(NetworkSocketProvider.class); Iterator<NetworkSocketProvider> iter = sl.iterator(); if (!iter.hasNext()) throw new RuntimeException("No service providers found!"); NetworkSocketProvider provider = iter.next(); return provider.openNetworkSocket(); } } $ cat src/com.socket/com/socket/spi/NetworkSocketProvider.java package com.socket.spi; import com.socket.NetworkSocket; public abstract class NetworkSocketProvider { protected NetworkSocketProvider() { } public abstract NetworkSocket openNetworkSocket(); }
下面是org.fastsocket模块的源码:
$ cat src/org.fastsocket/module-info.java module org.fastsocket { requires com.socket; provides com.socket.spi.NetworkSocketProvider with org.fastsocket.FastNetworkSocketProvider; } $ cat src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java package org.fastsocket; import com.socket.NetworkSocket; import com.socket.spi.NetworkSocketProvider; public class FastNetworkSocketProvider extends NetworkSocketProvider { public FastNetworkSocketProvider() { } @Override public NetworkSocket openNetworkSocket() { return new FastNetworkSocket(); } } $ cat src/org.fastsocket/org/fastsocket/FastNetworkSocket.java package org.fastsocket; import com.socket.NetworkSocket; class FastNetworkSocket extends NetworkSocket { FastNetworkSocket() { } public void close() { } }
为了简单起见,咱们一块儿编译这两个模块。在实践中,服务消费模块和服务提供模块几乎老是单独编译。
$ mkdir mods $ javac -d mods -modulesourcepath src $(find src -name "*.java")
最后咱们修改一下com.greetings模块,在其Main中使用上面提供出的API:
$ cat src/com.greetings/module-info.java module com.greetings { requires com.socket; } $ cat src/com.greetings/com/greetings/Main.java package com.greetings; import com.socket.NetworkSocket; public class Main { public static void main(String[] args) { NetworkSocket s = NetworkSocket.open(); System.out.println(s.getClass()); } } $ javac -d mods/com.greetings/ -mp mods $(find src/com.greetings/ -name "*.java")
最后,咱们运行一下:
$ java -mp mods -m com.greetings/com.greetings.Main class org.fastsocket.FastNetworkSocket
输出结果代表服务提供者已经找到,并且使用的是NetworkSocket工厂,实现是FastNetworkSocketProvider。
jlink 是链接器工具,用来链接一组模块 ,连同他们的依赖关系,建立一个自定义模块运行时镜像( JEP 220规范定义)。
该工具目前须要的封装成模块JAR或者JMOD格式的模块的路径。JDK中将一些标准以及JDK特性包装成JMOD格式(在JDK安装目录的jmods目录下)。
下面的示例建立一个运行时镜像包含模块com.greetings以及传递相关的依赖:
jlink --modulepath $JAVA_HOME/jmods:mlib --addmods com.greetings --output greetingsapp
--modulepath 是包含将要打包的模块的模块路径(示例中$JAVA_HOME/jmods
是JDK内置的模块,全部模块默认引入有java.base模块的依赖,java.base在$JAVA_HOME/jmods
中),Linux目录分隔符是:Windows是;按照本身的环境修改“jmods:mlib”的符号。(我在本地测试这个没有成功,有成功的同窗请留言)
$JAVA_HOME/jmods
是java.base.jmod的模块路径同时包含别的JDK模块.。若是你是本身编译的OpenJDK,那么应该使用 $BUILDOUTPUT/images/jmods
, $BUILDOUTPUT
指的是你设置的编译输出目录。
在模块路径设置里的目录mlib包含模块com.greetings。
jlink工具支持许多高级选项自定义生成的镜像, jlink --help
查看更多选项。
本人的直播课程在 7 月份就要开始了,但愿小伙伴们支持一下,如今报名有优惠噢