• Jigsaw 项目:Java 模块系统新手引导


    前言

    随着 2017 年 10 月 Java 9 的发布,Java 能够使用模块系统了,但是中文互联网上的资料太少,许多关于 Java 模块系统的文章都只是介绍了模块系统的好处,或者给了一些毫无组织的代码片段,新手在第一次使用模块系统时往往不知道如何下手。

    好在 OpenJDK 官方文档给出了一个很详细的新手引导,即使是从没使用过模块系统的人,按照新手引导也能完成自己的第一个 Java 模块。我在这里只是将其翻译成中文(英语水平有限,如有纰漏,欢迎指出),希望更多人能学会如何使用模块系统,加速 Java 类库的模块化进程。

    原文地址:Project Jigsaw: Module System Quick-Start Guide

    Jigsaw 项目:模块系统新手引导

    这篇文档给开始使用模块系统的开发者提供了一个简单示例。

    示例中的文件路径使用前斜杠(/),路径分隔符是冒号(:)。使用微软的 Windows 操作系统的开发者在路径中应当使用后斜杠(),路径分隔符为分号(;)。

    Greetings

    第一个示例是一个名叫com.greetings的模块,它只是简单的打印一句“Greetings”。这个模块由两个源文件组成:模块声明文件(module-info.java)和主类。

    为了方便,模块的源码放在一个和模块名相同的目录中。

    src/com.greetings/com/greetings/Main.java
    src/com.greetings/module-info.java
    
    $ cat src/com.greetings/module-info.java
    
    $ 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中去:

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

    现在我们用下面的命令来运行示例:

    $ java --module-path mods -m com.greetings/com.greetings.Main
    

    --module-path是模块路径,它的值是一个或多个包含模块的目录。-m选项指定主模块,分隔符后面的值是主模块中包含 main 方法的类的类名。

    Greetings world

    第二个示例更新了org.astro模块的模块声明文件来声明依赖。模块org.astro导出了 API 包org.astro

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

    模块被依次编译。编译com.greetings模块的javac命令指定了一个模块路径,所以对模块org.astro的引用、以及它所导出的包中的类型都可以被找到。

    $ mkdir -p 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 --module-path mods -d mods/com.greetings 
        src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
    

    这个示例的运行方式和第一个例子完全一样:

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

    多模块编译

    在前面的示例中,模块com.greetings和模块org.astro是分别编译的。其实在一个javac命令中编译多个模块也是可以的:

    $ mkdir mods
    
    $ javac -d mods --module-source-path 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
    

    打包

    目前为止,示例中被编译的模块散落在文件系统中。为了更方便的传输与部署,通常会将模块打包成一个modular JAR(模块化的 JAR 包)。一个 modular JAR 相当于一个包含了一个 module-info.class 在它的顶层目录的普通 JAR 包。下面的例子在目录 mlib 中创建了一个org.astro@1.0.jarcom.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 。模块com.greetings被打包时标识了它的主类是com.greetings.Main。我们现在可以不需要指定主类来执行模块com.greetings了:

    $ java -p mlib -m com.greetings
    Greetings world!
    

    通过使用-p来代替--module-path,命令可以被缩短。

    jar 工具有许多新的选项(通过jar -help查看),其中一个就是打印一个 modular JAR 的模块声明。

    $ jar --describe-module --file=mlib/org.astro@1.0.jar
    org.astro@1.0 jar:file:///d/mlib/org.astro@1.0.jar/!module-info.class
    exports org.astro
    requires java.base mandated
    

    缺少导入或缺少导出

    现在我们来看看对于前面的例子,如果在模块com.greetings的模块声明中,我们不小心漏写了引用项(requires)将会发生什么。

    $ cat src/com.greetings/module-info.java
    module com.greetings {
        // requires org.astro;
    }
    
    $ javac --module-path 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: error: package org.astro is not visible
        import org.astro.World;
                  ^
      (package org.astro is declared in module org.astro, but module com.greetings does not read it)
    1 error
    

    我们现在修复了这个模块声明,但是却引入了另一个错误,这次我们漏写了模块org.astro的模块声明中的导出项(exports):

    $ 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 --module-path mods -d mods/com.greetings 
        src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
    $ javac --module-path 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: error: package org.astro is not visible
        import org.astro.World;
                  ^
      (package org.astro is declared in module org.astro, which does not export it)
    1 error
    

    服务

    服务能够让服务消费者模块和服务提供者模块解耦。

    这个例子有一个服务消费者模块和一个服务提供者模块:
    - 模块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 --module-source-path src $(find src -name "*.java")
    

    最后我们修改我们的com.greetings模块来使用 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/ -p mods $(find src/com.greetings/ -name "*.java")
    

    最后我们来运行它:

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

    输出结果证明服务提供者是存在的,并且它被NetworkSocket作为工厂使用。

    链接

    jlink 是一个链接工具,可以沿着依赖链来链接一组模块,创建一个用户模块运行时镜像(见 JEP 220)。

    该工具目前要求模块路径中的模块都是被用 modular JAR 或者 JMOD 格式打包的。JDK 构建用 JMOD 格式打包标准的、和 JDK 指定的模块。

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

    jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp
    

    --module-path的值是包含了要打包的模块的 路径。在微软的 Windows 操作系统中要将路径分隔符 ':' 替换为 ';' 。

    $JAVA_HOME/jmods是包含了java.base.jmod和其他标准 JDK 模块的目录。

    模块路径中的mlib目录包含了模块com.greetings的部件(artifact)。

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

    --patch-module

    从 Doug Lea 的 CVS 中查看java.util.concurrent包中的 class 文件的开发者将会习惯使用-Xbootclasspath/p编译源文件和部署这些 class 文件。

    -Xbootclasspath/p已经被移除,在一个模块中,用来覆盖 class 文件的模块替换选项是--patch-module。它也可以被用于增加模块的内容。javac也支持在编译代码时加上--patch-module选项,“犹如”某个模块的一部分一样。

    这里有一个编译新版本的java.util.concurrent.ConcurrentHashMap并且在运行时使用它的例子:

    javac --patch-module java.base=src -d mypatches/java.base 
        src/java.base/java/util/concurrent/ConcurrentHashMap.java
    
    java --patch-module java.base=mypatches/java.base ...
    

    更多信息

    from: https://zhuanlan.zhihu.com/p/41129220

  • 相关阅读:
    mysql官网下载yum
    zookeeper和kafka的leader和follower
    查看目标端口是否被占用
    scala中的val,var和lazy
    scala的异常处理try catch
    Navicat总是提示主键不存在问题
    idea常用快捷键
    wiremock技术入门
    Liunx常用操作(11)-VI编辑器-末行模式命令
    Liunx常用操作(十)-VI编辑器-命令模式命令
  • 原文地址:https://www.cnblogs.com/GarfieldEr007/p/9940722.html
Copyright © 2020-2023  润新知