Java9特性預覽Jigsaw:模塊化系統快速入門指南

##轉載請標明出處 http://coderknock.com 本文檔提供了幾個簡單的例子,讓開發人員開始使用模塊。java

例子中的文件路徑使用斜槓,路徑分隔符是冒號。使用微軟Windows開發的人員應該使用文件路徑以反斜槓和一個分號做爲路徑分隔符。 ####Greetingsshell

第一個例子是一個只打印「Greetings!」命名爲com.greetings的模塊。該模塊包括兩個源文件:模塊聲明文件(module-info.java)和Main.java網絡

按慣例,模塊的源代碼在一個目錄中,該目錄是模塊的名稱:app

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 目錄下:socket

$ mkdir -p mods/com.greetings

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

如今咱們用下面的命令運行這個例子:ide

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

-modulepath是模塊的路徑,它的值是一個或多個包含模塊的目錄。-m 選項指定了主模塊,在/後的值是模塊主類包含包名的完整名稱。 ####Greetings world模塊化

第二個示例是在以前示例的基礎上增長了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

該示例以與第一個示例徹底相同的方式運行:ui

$ 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

####Packaging

到目前爲止的示例中,已編譯的模塊的內容在文件系統上以分散的文件的形式存儲。當用於分發和部署時一般更方便的方式是將一個模塊打包成一個模塊化的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

####缺乏requires(依賴) 或者exports(輸出)

若是咱們在上一個示例中在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 指的是你設置的編譯輸出目錄。

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查看更多選項。

相關文章
相關標籤/搜索