Jigsaw:模块系统快速入门指南

来源:
三产
最后修订:
2016年06月28日 00:00:00
 861

Project Jigsaw: Module System Quick-Start Guide

项目拼图:模块系统快速入门指南

This document provides a few simple examples to get developers started with modules. 本文档提供了几个简单的例子,让...

Project Jigsaw: Module System Quick-Start Guide

项目拼图:模块系统快速入门指南

This document provides a few simple examples to get developers started with modules. 本文档提供了几个简单的例子,让开发人员开始使用模块。

The file paths in the examples use forward slashes, and the path separators are colons. Developers on Microsoft Windows should use file paths with back slashes and a semi-colon as the path separator. 例子中的文件路径使用斜杠,路径分隔符是冒号。使用微软Windows开发的人员应该使用文件路径以反斜杠和一个分号作为路径分隔符。

Greetings

This first example is a module named com.greetings that simply prints “Greetings!“. The module consists of two source files: the module declaration (module-info.java) and the main class. 第一个例子是一个只打印“Greetings!”命名为com.greetings的模块。该模块包括两个源文件:模块声明文件(module-info.java)和Main.java

By convention, the source code for the module is in a directory that is the name of the module. 按惯例,模块的源代码在一个目录中,该目录是模块的名称:

      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!");
        }
    }

The source code is compiled to the directory mods/com.greetings with the following commands: 使用以下命令,将源码编译到 mods/com.greetings 目录下:

    $ mkdir -p mods/com.greetings

    $ javac -d mods/com.greetings src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java

Now we run the example with the following command: 现在我们用下面的命令运行这个例子:

    $ java -modulepath mods -m com.greetings/com.greetings.Main

-modulepath is the module path, its value is one or more directories that contain modules. The -m option specifies the main module, the value after the slash is the class name of the main class in the module. -modulepath是模块的路径,它的值是一个或多个包含模块的目录。-m 选项指定了主模块,在/后的值是模块主类包含包名的完整名称。

Greetings world

This second example updates the module declaration to declare a dependency on module org.astro. Module org.astro exports the API package org.astro.

第二个示例是在之前示例的基础上增加了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());
        }
    }

The modules are compiled, one at a time. The javac command to compile module com.greetings specifies a module path so that the reference to module org.astro and the types in its exported packages can be resolved.

该模块编译,在同一时间。使用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                              

The example is run in exactly the same way as the first example:

该示例以与第一个示例完全相同的方式运行:

    $ java -modulepath mods -m com.greetings/com.greetings.Main
    Greetings world!

Multi-module compilation

多模块编译

In the previous example then module com.greetings and module org.astro were compiled separately. It is also possible to compile multiple modules with one javac command:

在前面的示例,模块 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

Packaging

In the examples so far then the contents of the compiled modules are exploded on the file system. For transportation and deployment purposes then it is usually more convenient to package a module as a modular JAR. A modular JAR is a regular JAR file that has a module-info.class in its top-level directory. The following example creates org.astro@1.0.jar and com.greetings.jar in directory mlib.

到目前为止的示例中,已编译的模块的内容在文件系统上以分散的文件的形式存储。当用于分发和部署时通常更方便的方式是将一个模块打包成一个模块化的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

In this example, then module org.astro is packaged to indicate that its version is 1.0. Module com.greetings has been packaged to indicate that its main class is com.greetings.Main. We can now execute module com.greetings without needing to specify its main class:

在这个例子中,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!

The command line is also shortened by using -mp as an alternative to -modulepath.

上面的命令中使用了 -modulepath 的简写 -mp

The jar tool has many new options (see jar -help), one of which is to print the module declaration for a module packaged as a modular JAR.

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

Missing requires or missing exports

缺少requires(依赖) 或者exports(输出)

Now let's see what happens with the previous example when we mistakenly omit the requires from the com.greetings module declaration:

如果我们在上一个示例中在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 个错误

We now fix this module declaration but introduce a different mistake, this time we omit the exports from the org.astro module declaration:

现在我们试一下,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 个错误

Services

服务

Services allow for loose coupling between service consumers modules and service providers modules.

服务允许服务消费者模块和服务提供商模块之间的松散耦合。

This example has a service consumer module and a service provider module:

这个例子有一个服务消费模块和一个服务提供模块:

module com.socket exports an API for network sockets. The API is in package com.socket so this package is exported. The API is pluggable to allow for alternative implementations. The service type is class com.socket.spi.NetworkSocketProvider in the same module and thus package com.socket.spi is also exported.

模块com.socket输出了一个网络套接字的API。API被封装在com.socket包,所以这个包是输出者。API是可插拔的,允许替换具体的实现。com.socket.spi.networksocketprovider是实际提供服务的抽象类,所以com.socket.spi也应该被输出。

module org.fastsocket is a service provider module. It provides an implementation of com.socket.spi.NetworkSocketProvider. It does not export any packages.

org.fastsocket模块是一个服务提供模块,它提供了一个com.socket.spi.NetworkSocketProvider的实现,不需要输出。

The following is the source code for module com.socket .

下面是 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();
    }

The following is the source code for module org.fastsocket . 下面是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() { }
    }

For simplicity, we compile both modules together. In practice then the service consumer module and service provider modules will nearly always be compiled separately. 为了简单起见,我们一起编译这两个模块。在实践中,服务消费模块和服务提供模块几乎总是单独编译。

    $ mkdir mods
    $ javac -d mods -modulesourcepath src $(find src -name "*.java")

Finally we modify our module com.greetings to use the API.

最后我们修改一下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")

Finally we run it: 最后,我们运行一下:

    $ java -mp mods -m com.greetings/com.greetings.Main
    class org.fastsocket.FastNetworkSocket

The output confirms that the service provider has been located and that it was used as the factory for the NetworkSocket.

输出结果表明服务提供者已经找到,而且使用的是NetworkSocket工厂,实现是FastNetworkSocketProvider。

The linker

链接器

jlink is the linker tool and can be used to link a set of modules, along with their transitive dependences, to create a custom modular run-time image (see JEP 220).

jlink 是连接器工具,用来连接一组模块 ,连同他们的依赖关系,创建一个自定义模块运行时镜像( JEP 220规范定义)。

The tool currently requires that modules on the module path be packaged in modular JAR or JMOD format. The JDK build packages the standard and JDK-specific modules in JMOD format.

该工具目前需要的封装成模块JAR或者JMOD格式的模块的路径。JDK中将一些标准以及JDK特性包装成JMOD格式(在JDK安装目录的jmods目录下)。

The following example creates a run-time image that contains the module com.greetings and its transitive dependences:

下面的示例创建一个运行时镜像包含模块com.greetings以及传递相关的依赖:

    jlink --modulepath $JAVA_HOME/jmods:mlib --addmods com.greetings --output greetingsapp

The value to –modulepath is a PATH of directories containing the packaged modules. Replace the path separator ':' with ';' on Microsoft Windows.

–modulepath 是包含将要打包的模块的模块路径(示例中$JAVA_HOME/jmods是JDK内置的模块,所有模块默认引入有java.base模块的依赖,java.base在$JAVA_HOME/jmods中),Linux目录分隔符是:Windows是;按照自己的环境修改“jmods:mlib”的符号。(我在本地测试这个没有成功,有成功的同学请留言)

$JAVA_HOME/jmods is the directory containing java.base.jmod and the other standard and JDK modules. If you using your own build of OpenJDK then the jmod files are in $BUILDOUTPUT/images/jmods, where $BUILDOUTPUT is the build output directory.

$JAVA_HOME/jmods是java.base.jmod的模块路径同时包含别的JDK模块.。如果你是自己编译的OpenJDK,那么应该使用 $BUILDOUTPUT/images/jmods, $BUILDOUTPUT 指的是你设置的编译输出目录。

The directory mlib on the module path contains the artifact for module com.greetings.

在模块路径设置里的目录mlib包含模块com.greetings。

The jlink tool supports many advanced options to customize the generated image, see jlink –help for more options.

jlink工具支持许多高级选项自定义生成的镜像,jlink --help查看更多选项。

翻译:http://coderknock.com/blog/2016/06/27/Java%E6%A8%A1%E5%9D%97%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%88%9D%E6%8E%A2.html 原文:http://openjdk.java.net/projects/jigsaw/quick-start