java9 模塊化 jigsaw



java9並沒有在語言層面做出很多改變,而是致力於一些新特性,如模塊化,其核心就是解決歷史遺留問題,為以后的jar包森林理清道路。模塊化是一個很大的命題,就不講那么細致了,關於java9的特性也有很多書籍可供參考,如
《Java 9 Revealed: For Early Adoption and Migration》《Java 9 Modularity》還有很多很多(點擊查看)

模塊化是一個很大的特性,也是一個很大的話題。模塊化作為一個未來的的趨勢,我們可能會關心項目的遷移,對接OSGI或者微服務等等,本文只是簡單闡述下模塊化解決的基本問題和模塊化的一些用法。

模塊化

兩個問題,臃腫的jar包和混亂的jar包依賴關系。如果我們沒被現在的jar包系統搞瘋,首先應該感謝IDE和maven,gradle之類的版本控制軟件。你可能見過這個大家伙,60多m的jar包,運行一個Hello World程序,也需要這么一個龐然大物支撐。

nothing

再或者,我們依賴一個大的工具包,但我們可能只需要其中幾個類的功能。之前的jar包機制是很僵硬的,我們需要更靈活更輕便條理更清晰的環境,從化繁為簡的角度來講,我們需要模塊化。

不知道你經歷過jar hell嗎,可以參考下圖:

此處輸入圖片的描述

jar包之間的依賴可謂錯綜復雜,jar包之間交叉,纏繞,沖突,旋轉,跳躍,我覺得沒有人會想理清項目中依賴的這一個jar包與其他Jar包之間千絲萬縷的聯系。不看依賴,便是晴天,但是我們有時候真的需要理清楚,因為我們在部署的時候可能會產生沖突,會報各種異常。我們理清楚了,我們對這個jar包理解更深了,我們變強了,我們禿了。我們需要更清晰的圖像來描述依賴關系,我們不能任由jar纏繞旋轉,我們應該讓他們更守規矩,讓依賴圖像清楚並且有層次,我們需要模塊化。

其實我們不需要理解模塊化的字面意義,我們需要知道我們面臨的困難和要解決的問題,jdk9發布的解決這些問題的新特性的名稱叫做模塊化。

漫長的模塊化

在使用模塊化之前,我需要讓你明白一些事情,模塊化不是一蹴而就,java平台做了表率,從內部進行了模塊化改造。我們先來看一下傳統的jdk7的API:

此處輸入圖片的描述

很熟悉的api形式。

再來看一看java9的api:

此處輸入圖片的描述

點進java.activation

此處輸入圖片的描述

我們還可以看到模塊的圖像,Java9不僅為我們提供了模塊化的手段,整個java9平台自身,也已經被模塊化改造了。整個java生態徹底改造成模塊化是需要時間的,從jdk7時提出模塊化概念,到整個模塊化版本發布,經歷了很長時間。把原來的第三方jar包徹底改造成模塊化jar包會花費很多功夫,我們能夠使用jdk的模塊化包,不代表我們能夠使用整個Java生態中所有的模塊化包,這需要開發者對自己的jar包進行調整。

  • 模塊化為我們梳理出了邏輯清晰的jar包系統。
  • 模塊化需要額外的操作,處理相同的代碼,我們需要理清代碼之間的關系,對內容模塊化,但IDE提供了一些便捷的功能。
  • 擁抱徹底的模塊化生態需要時間,很多重量級的框架和jar包適應模塊化是需要我們等待的,但是有自動化模塊可以幫助我們構建模塊化項目。

簡單入門模塊化

$JAVA_HOME/bin中的Java工具也為模塊化提供了很多新功能,就不一一詳細介紹了,感興趣的話可以參考Oracle的介紹。下面的例子主要是參考openJDK模塊化的快速入門GitHub上的一個項目,很好的例子,我也就不做太多更改了,會簡要敘述下例子中的新事物。

首先要下載JDK9,可以去官網下載,我是用Linux平台演示的代碼。

先來看第一個例子,輸出Greetings!

先來看一看整個項目的目錄結構:

src
└── src/com.greetings
    ├── src/com.greetings/com
    │   └── src/com.greetings/com/greetings
    │       └── src/com.greetings/com/greetings/Main.java
    └── src/com.greetings/module-info.java

整個項目包含兩個文件,一個是module-info.java模塊文件,還有一個是Main.java主類文件。讓我們看一看這兩個文件的內容。

$ cat src/com.greetings/module-info.java
    module com.greetings { }

模塊文件中沒有內容,因為這個Main.java中沒有依賴別的模塊的內容,也沒有打算向外界暴露Main.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!");
    }
}

然后,我們把這兩個文件編譯運行。

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

javac -d 指定放置生成的類文件的位置

java --module-path mods \
-m com.greetings/com.greetings.Main 
#或者
java --module-path mods \
--module com.greetings/com.greetings.Main

就可以運行了,我們會發現屏幕上打印出了Greetings!

其中 --module-path <模塊路徑>...用 : 分隔的目錄列表, 每個目錄都是一個包含模塊的目錄。

-m或--module是一樣的,用於指定要解析的初始化模塊名稱,如果不指定模塊名稱,可以指定要執行的主類名稱

再來看第二個例子,Greetings world!

我們先來看一下目錄結構:

src
├── src/com.greetings
│   ├── src/com.greetings/com
│   │   └── src/com.greetings/com/greetings
│   │       └── src/com.greetings/com/greetings/Main.java
│   └── src/com.greetings/module-info.java
└── src/org.astro
    ├── src/org.astro/module-info.java
    └── src/org.astro/org
        └── src/org.astro/org/astro
            └── src/org.astro/org/astro/World.java

再來看看代碼內容:

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

cat src/com.greetings/module-info.java

module com.greetings { 
        requires org.astro;
}

cat src/org.astro/org/astro/World.java

package org.astro;

public class World {
        public static String name() {
                return "world";
        }
}

cat src/org.astro/module-info.java

module org.astro { 
        exports org.astro;
}

Main.java類中打印了Greetings!+World.name()com.greetings模塊文件聲明了requires org.astro,意思就是我這個模塊需要依賴org.astro模塊;World類中有一個靜態方法,返回了world字符串,com.greetings模塊依賴於它,而且他在自己的模塊文件中做出了聲明exports org.astro,將org.astro模塊暴露出去,可供其他模塊依賴。

#我們先編譯com.greetings模塊試試

javac -d mods/com.greetings \
> >  src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java:2: 錯誤: 找不到模塊: org.astro
        requires org.astro;
                    ^
1 個錯誤

那再先編譯一下,org.astro模塊

 javac -d mods/org.astro \
> src/org.astro/module-info.java src/org.astro/org/astro/World.java

然后編譯一下com.greeetings

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

--module-path(或-p) 用來指定應用的模塊的位置。

運行一下

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

這個例子中要注意exportsrequires的用法,exports對外界暴露內容,requires引入外界功能,如果沒有引入,或者引入不存在的module編譯無法通過。

#如果沒有聲明requires
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
#如果沒有聲明exports
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

多模塊的編譯

可以一次編譯多個模塊

我們先找出所有.java文件

echo $(find src -name "*.java")
//src/org.astro/org/astro/World.java src/org.astro/module-info.java src/com.greetings/com/greetings/Main.java src/com.greetings/module-info.java

一口氣將所有.java文件編譯到mods文件夾中

javac -d mods --module-source-path src $(find ./src -name "*.java")

看一下目錄結構

mods
├── mods/com.greetings
│   ├── mods/com.greetings/com
│   │   └── mods/com.greetings/com/greetings
│   │       └── mods/com.greetings/com/greetings/Main.class
│   └── mods/com.greetings/module-info.class
└── mods/org.astro
    ├── mods/org.astro/module-info.class
    └── mods/org.astro/org
        └── mods/org.astro/org/astro
            └── mods/org.astro/org/astro/World.class

效果是一樣的,再運行一下

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

  • 打包

我們可以把模塊打成jar包,這樣會更方便,模塊jar包里頂級目錄下會有一個module-info.calss文件。

我們把已經編譯好的mods文件夾中的模塊文件打包成jar包

注意:最后的句號不能漏掉

jar -c -f mlib/org.astro@1.0.jar \
        --module-version=1.0 -C mods/org.astro .
//或者 
jar --create --file=mlib/org.astro@1.0.jar \
        --module-version=1.0 -C mods/org.astro .

兩個都可以的,--create縮寫就是-c,意思是我們創建jar包;--file=縮寫就是-f,指定文件位置和名稱,--module-version是指定版本,創建模塊化 jar 或更新非模塊化 jar 時的模塊版本;-C 就是更改為指定的目錄並包含以下文件。關於jar命令還有很多操作,可以通過jar --help查閱,或者去官網查閱。

$ jar --create --file=mlib/com.greetings.jar \
        --main-class=com.greetings.Main -C mods/com.greetings .

產看一下生成的jar包

$ ls mlib
com.greetings.jar   org.astro@1.0.jar

來執行一下,因為我們打包com.greetings模塊時,指定了主類,所以運行時可以直接調用

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

Service

如何使用模塊系統提供服務呢,讓我們來看看。

首先查看一下工程的結構:

src
├── src/com.greetings
│   ├── src/com.greetings/com
│   │   └── src/com.greetings/com/greetings
│   │       └── src/com.greetings/com/greetings/Main.java
│   └── src/com.greetings/module-info.java
├── src/com.socket
│   ├── src/com.socket/com
│   │   └── src/com.socket/com/socket
│   │       ├── src/com.socket/com/socket/NetworkSocket.java
│   │       └── src/com.socket/com/socket/spi
│   │           └── src/com.socket/com/socket/spi/NetworkSocketProvider.java
│   └── src/com.socket/module-info.java
└── src/org.fastsocket
    ├── src/org.fastsocket/module-info.java
    └── src/org.fastsocket/org
        └── src/org.fastsocket/org/fastsocket
            ├── src/org.fastsocket/org/fastsocket/FastNetworkSocket.java
            └── src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java

有點多,我們先理一理其中的關系
image
com.scoket中還有個NetworkSocketProvider.java,其實就是用來創建socket的一個工廠。我們再來看看他們的代碼:

先來看看org.socket模塊:

package com.socket;

import com.socket.spi.NetworkSocketProvider;

import java.io.Closeable;
import java.util.Iterator;
import java.util.ServiceLoader;

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

其中有一個open()方法,其實就是使用provider生成一個NetworkSocket對象並返回。

再來看看NetworkSocketProvider:

package com.socket.spi;

import com.socket.NetworkSocket;

public abstract class NetworkSocketProvider {
    protected NetworkSocketProvider() {}

    public abstract NetworkSocket openNetworkSocket();
}

一個抽象類,openNetworkSocket()抽象方法由子類實現。

module-info.java

module com.socket {
        exports com.socket;
        exports com.socket.spi;
        uses com.socket.spi.NetworkSocketProvider;
}

我想上面兩個exports你應該能夠理解其意義,但是uses是什么意思呢。為了使用服務,它的提供者需要被發現和加載。java.util.ServiceLoader類來發現和加載服務。發現和加載服務提供者的模塊必須在其聲明中包含一個uses語句,該語句語法如下:
uses <service-interface>;<service-interface>是服務接口的名稱,它是Java接口名稱,類名稱或注解類型名稱。如果一個模塊使用ServiceLoader<S>類加載名為S的服務接口的服務提供者的實例,則模塊聲明必須包含以uses S。總而言之,就是你如果是個服務接口,你需要服務提供的類實現你的接口,你就需要聲明uses

那我們在看一看服務提供者類,org.fastsocket:

//繼承了NetWorkSocket類
package org.fastsocket;

import com.socket.NetworkSocket;

public class FastNetworkSocket extends NetworkSocket {
    FastNetworkSocket() {}
    public void close() {}
}

再來看看FastNetworkSocketProvider類,它實現了NetworkSocketProvider抽象類,重寫了抽象方法,用來返回一個NetworkSocket。

package org.fastsocket;

import com.socket.NetworkSocket;
import com.socket.spi.NetworkSocketProvider;

public class FastNetworkSocketProvider extends com.socket.spi.NetworkSocketProvider {
    public FastNetworkSocketProvider() {}

    @Override
    public NetworkSocket openNetworkSocket() {
        return new FastNetworkSocket();
    }
}

再來看看module-info.java

module org.fastsocket {
        requires com.socket;
        provides com.socket.spi.NetworkSocketProvider
                with org.fastsocket.FastNetworkSocketProvider;
}

其中有個provideswith,意思就是為NetworkSocketProvider提供一個實現類org.fastsocket.FastNetworkSocketProvider

再來快速看一下客戶端代碼,我們運行一下整個服務系統。

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

cat src/com.greetings/module-info.java
module com.greetings {
        requires com.socket;
}

客戶端代碼很簡單,客戶端模塊依賴了com.socketMain.java打印了提供服務的類名。

我們先試着編譯服務接口代碼和客戶端代碼:

javac  -d mods/com.socket  $(find src/com.socket -name "*.java")
javac --module-path mods -d mods/com.greetings  $(find src/com.greetings -name "*.java")    

編譯時並沒有報錯。是的,服務接口中雖然聲明了uses關鍵字,意味着這個模塊需要實現類,但是在編譯時他不會尋找需要的實現類模塊。

運行一下看看。

java -p mods -m com.greetings/com.greetings.Main

Exception in thread "main" java.lang.RuntimeException: No service providers found!
        at com.socket/com.socket.NetworkSocket.open(NetworkSocket.java:16)
        at com.greetings/com.greetings.Main.main(Main.java:7)

報了一個運行時異常,意味着對於服務的具體實現是在運行是尋找的。

我們把實現類編譯一下,然后運行:

javac --module-path mods -d mods/org.fastsocket $(find src/org.fastsocket -name "*.java") 

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

成功打印出了類名,是不是有點像OSGI的熱插拔。通過模塊系統,我們實現了對服務接口和服務提供者的解耦,我們確保服務提供者在調用服務之前能夠編譯到模塊中即可。

模塊補丁

什么是模塊補丁呢,假如我們的項目依賴了一個A模塊,但是覺得需要自定義A模塊中某些類的功能,那可以修改這個類,然后把修改后的類打到依賴的A模塊中。這樣,我們的項目就可以使用擁有自定義類的模塊了。下面以ConcurrentHashMap為例,ConcurrentHashMapjava.base模塊中的一個類,我們將自定義的ConcurrentHashMap類替換java.baseConcurrentHashMap

先來看一看整個工程的結構:

├── src/com.greetings
│   ├── src/com.greetings/com
│   │   └── src/com.greetings/com/greetings
│   │       └── src/com.greetings/com/greetings/Main.java
│   └── src/com.greetings/module-info.java
└── src/java.base
    └── src/java.base/java
        └── src/java.base/java/util
            └── src/java.base/java/util/concurrent
                └── src/java.base/java/util/concurrent/ConcurrentHashMap.java

其中有兩個模塊,com.greeting模塊中依賴了java.base中的ConcurrentHashMapjava.base模塊里包含了我們重寫的ConcurrentHashMap類,而且注意一點,這個模塊中沒有module-info.java描述文件。再來看看模塊的內容:

module com.greetings {
}

package com.greetings;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> myMap = new ConcurrentHashMap<>();
        myMap.put("one", 1);
        myMap.put("two", 2);
        myMap.put("three", 3);
        System.out.println("Hello " + myMap);
        System.out.println("Is Duke home? " + myMap.containsKey("Duke"));
    }
}

雖然module-info.java中沒有聲明依賴java.base,但是其實所有的模塊都是隱式依賴java.base模塊的,java.base是所有模塊的基本模塊,他暴露了java.langjava.util等等基礎包。Main.java中的內容也很簡單,放入ConcurrentHashMap中幾個元素,然后打印。

再來看看我們重寫的ConcurrentHashMap的內容,可以只看put()方法,containsKey()方法和toString()方法:

package java.util.concurrent;

import java.io.Serializable;
import java.util.*;

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable {



    private final Map<K, V> innerMap;

    public ConcurrentHashMap() {
        this.innerMap = new HashMap<>();
    }

    public ConcurrentHashMap(int initialCapacity) {
        this.innerMap = new HashMap<>(initialCapacity);
    }

    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        this.innerMap = new HashMap<>(initialCapacity, loadFactor);
    }

    public Set<Map.Entry<K, V>> entrySet() {
        return innerMap.entrySet();
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        return innerMap.replace(key, oldValue, newValue);
    }

    @Override
    public V replace(K key, V value) {
        return innerMap.replace(key, value);
    }

    @Override
    public V remove(Object o) {
        return innerMap.remove(o);
    }

    @Override
    public boolean remove(Object key, Object value) {
        return innerMap.remove(key, value);
    }

    @Override
    public V putIfAbsent(K key, V value) {
        return innerMap.putIfAbsent(key, value);
    }

    @Override
    public V put(K key, V value) {
        return innerMap.put(key, value);
    }

    @Override
    public V get(Object key) {
        return innerMap.get(key);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        innerMap.putAll(map);
    }
    @Override
    public boolean containsKey(Object key) {
        if (Objects.equals(key, "Duke")) {
            return true;
        }
        return super.containsKey(key);
    }

    public String toString() {
        return "patched ConcurrentHashMap " + super.toString() + " + Duke";
    }
}

好了,這就是我們自定義的ConcurrentHashMap類,我們先運行一下不打補丁的com.greetings模塊。

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 \
     --module com.greetings/com.greetings.Main

Hello {one=1, two=2, three=3}
Is Duke home? false

在沒有補丁的情況下,使用的是原來的ConcurrentHashMap類。

試一試對java.base打上補丁:

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


java --module-path mods \
     --patch-module java.base=mypatches/java.base \
      --module com.greetings/com.greetings.Main
      
Hello patched ConcurrentHashMap {one=1, two=2, three=3} + Duke
Is Duke home? true

成功了!我們在運行Main方法時候,使用的是自定義的ConcurrentHashMap類,來看看是如何做到的。首先在編譯的時候,我們使用了--patch-module這個命令,查看一下javac的幫助文檔javac --help-extra可以看到這個命令的解釋

--patch-module <模塊>=<文件>(:<文件>)*,使用 JAR文件或目錄中的類和資源覆蓋

所以呢,我們把java.base中的ConcurrentHashMap類給覆蓋了,在運行時,也有一個--patch-module,其實和上面的解釋是一樣的,運行時使用mypatches/java.base文件覆蓋java.base文件,然后得到了想要的結果。

自動模塊化

當我們已經開始模塊化的工程,但是需要依賴一些非模塊化的jar包,這些庫的開發人員並沒有及時更新庫使其適應模塊化,他們缺少模塊化的描述符,所以我們需要自動模塊化--將非模塊化的jar包轉化成模塊化jar。以junit為例,我們先來看看junitjar包的描述信息。

jar -d --file=lib/junit-4.12.jar 
找不到模塊描述符。已派生自動模塊。

junit@4.12 automatic
requires java.base mandated
contains junit.extensions
contains junit.framework
contains junit.runner
contains junit.textui
contains org.junit
...

-d等同於--describe-module,用於輸出模塊描述符或自動模塊名稱,--file=指定文件名,我們可以看到他為我們派生出junit的版本和描述符。但是不幸的是,他沒有告訴我們junit其實還依賴了Hamcrest,我們通過jdeps工具來查看jar包的依賴。

jdeps -s lib/junit-4.12.jar 
junit-4.12.jar -> java.base
junit-4.12.jar -> java.management
junit-4.12.jar -> 找不到

-s只是查看以來的概覽,有些依賴是不確定的,那我們看看丟失了哪些依賴:

jdeps lib/junit-4.12.jar | grep "找不到"   
junit-4.12.jar -> 找不到
   org.junit                                          -> org.hamcrest                                       找不到
   org.junit.experimental.results                     -> org.hamcrest                                       找不到
   org.junit.internal                                 -> org.hamcrest                                       找不到
   org.junit.internal.matchers                        -> org.hamcrest                                       找不到
   org.junit.matchers                                 -> org.hamcrest                                       找不到
   org.junit.matchers                                 -> org.hamcrest.core                                  找不到
   org.junit.rules                                    -> org.hamcrest                                       找不到

找到了!那我們看看Hamcrest需要哪些依賴:

jar -d -f lib/hamcrest-core-1.3.jar 
找不到模塊描述符。已派生自動模塊。

hamcrest.core@1.3 automatic
requires java.base mandated
contains org.hamcrest
contains org.hamcrest.core
contains org.hamcrest.internal


jdeps -s lib/hamcrest-core-1.3.jar
hamcrest-core-1.3.jar -> java.base

Hamcrest沒有缺失的依賴了,下面我們看看src文件的內容:

src
└── src/com.greetings
    ├── src/com.greetings/main
    │   └── src/com.greetings/main/java
    │       └── src/com.greetings/main/java/com
    │           └── src/com.greetings/main/java/com/greetings
    │               ├── src/com.greetings/main/java/com/greetings/Greet.java
    │               └── src/com.greetings/main/java/com/greetings/Main.java
    ├── src/com.greetings/module-info.java
    └── src/com.greetings/test
        └── src/com.greetings/test/java
            └── src/com.greetings/test/java/com
                └── src/com.greetings/test/java/com/greetings
                    └── src/com.greetings/test/java/com/greetings/GreetTest.java

這是src的目錄結構,然后再看看四個文件的內容:

cat src/com.greetings/module-info.java
module com.greetings {
        //1:聲明需要的模塊
        //2:設置對自動化模塊可訪問
}

cat src/com.greetings/main/java/com/greetings/Greet.java
package com.greetings;

import java.util.Arrays;
import java.util.stream.Collectors;

public class Greet {

    String greeting(String... names) {
        if ((names == null) || (names.length == 0)) {
            return "Hello World!";
        } else {
            return Arrays.stream(names)
                    .map(name -> String.format("Hello %s!", name))
                    .collect(Collectors.joining("\n"));
        }
    }

}

cat src/com.greetings/main/java/com/greetings/Main.java
package com.greetings;

public class Main {

    public static void main(String[] args) {
        Greet greet = new Greet();
        System.out.println(greet.greeting(args));
    }

}

cat src/com.greetings/test/java/com/greetings/GreetTest.java
package com.greetings;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class GreetTest {

    @Test
    public void greetingNobody_greetsTheWorld() {
        Greet greet = new Greet();

        assertEquals(
                greet.greeting(),
                "Hello World!"
        );
    }

    @Test
    public void greetingEmptyArray_greetsTheWorld() {
        Greet greet = new Greet();

        assertEquals(
                greet.greeting(new String[0]),
                "Hello World!"
        );
    }

    @Test
    public void greetingOnePerson_greetsThatPerson() {
        Greet greet = new Greet();

        assertEquals(
                greet.greeting("Alice"),
                "Hello Alice!"
        );
    }

    @Test
    public void greetingSeveralPeople_greetsAllOfThem() {
        Greet greet = new Greet();

        assertEquals(
                greet.greeting("Alice", "Bob", "Charlie"),
                "Hello Alice!\nHello Bob!\nHello Charlie!"
        );
    }

    @Test
    public void greetingArrayOfPeople_greetsAllOfThem() {
        Greet greet = new Greet();

        assertEquals(
                greet.greeting(new String[]{"Alice", "Bob", "Charlie"}),
                "Hello Alice!\nHello Bob!\nHello Charlie!"
        );
    }
}

內容其實挺簡單,打印Hello,world或者打印Hello,人名,在module-info.java文件中設置了一個小問題,可以試着填一填,答案在文章末尾。大體內容我們知道了,可是我們應該怎么編譯呢:

javac [3:設置自動化模塊路徑] \
      -d mods/main/com.greetings \
      src/com.greetings/module-info.java \
      src/com.greetings/main/java/com/greetings/Greet.java \
      src/com.greetings/main/java/com/greetings/Main.java

答案也在文章末尾,可以自己試着編譯一下。我們來運行一下:

java --module-path mods/main:lib \
          --module com.greetings/com.greetings.Main
//Hello World!

java --module-path mods/main:lib \
         --module com.greetings/com.greetings.Main \
        Alice Bob Charlie
//Hello Alice!
//Hello Bob!
//Hello Charlie!

再來編譯一下GreetingTest類:

javac --module-path mods:lib \
          -d mods/test/com.greetings \
          src/com.greetings/module-info.java \
          src/com.greetings/main/java/com/greetings/Greet.java \
          src/com.greetings/test/java/com/greetings/GreetTest.java

運行一下:

java --module-path mods/main:lib \
         --add-modules com.greetings \
         --patch-module com.greetings=mods/test/com.greetings \
         --module junit/org.junit.runner.JUnitCore \
        com.greetings.GreetTest
//JUnit version 4.12
.....
Time: 0.013

OK (5 tests)

完成!將非模塊化jar包轉換成自動模塊化jar包大體就是這樣了,如果遇見不會的java命令,可以通過--help查看說明。

jlink有什么用呢,其實很簡單,我們可以通過jlink構造自己的jre,前文提到rt.jar包很臃腫,jlink可以幫助我們構建精巧的運行環境。那我們先來看看整個項目的內容,內容很簡單,和第二個小測試一樣,可以快速瀏覽一下,主要是為了練習使用jlink工具的使用:

//結構目錄
src
├── src/com.greetings
│   ├── src/com.greetings/com
│   │   └── src/com.greetings/com/greetings
│   │       └── src/com.greetings/com/greetings/Main.java
│   └── src/com.greetings/module-info.java
└── src/org.astro
    ├── src/org.astro/module-info.java
    └── src/org.astro/org
        └── src/org.astro/org/astro
            └── src/org.astro/org/astro/World.java
            
//文件內容
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());
        }
}

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

那我們編譯一下:

javac -d mods/com.greetings \
        src/org.astro/module-info.java \
        src/org.astro/org/astro/World.java
        
javac --module-path mods \
      -d mods/org.astro \
      src/com.greetings/module-info.java \
      src/com.greetings/com/greetings/Main.java

jlink工具目前要求模塊路徑上的模塊以模塊化JAR或JMOD格式打包,所以我們將他們打成jar包:

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 .

好了,已經打成模塊化jar包了,開始jlink!

 jlink --module-path "${JAVA_HOME}"/jmods:mlib \
        --add-modules com.greetings \
        --output executable

完成!我們把執行文件輸出到了excutable文件夾,如果對這些命令選項不太理解,可以通過jlink --help來查看命令解釋。我們現在已經可以執行了:


./executable/bin/java --module-path mods --module com.greetings/com.greetings.Main
//Greetings World!
./executable/bin/java -jar mlib/com.greetings.jar com.greetings.Main
//Greetings World!

是不是很神奇,我們使用了executable中的java命令,對,他就是自定義的jre環境。如果感興趣可以查看一下excutable中的結構。

jmod

jmod可以對模塊或jar包壓縮的一個工具,但他的格式和jar包不一樣。

直接在上個練習目錄中試用命令就好。

jmod create \
              --class-path mods/com.greetings:mods/org.astro \
              greetings.jmod

然后查看一下:

jmod list greetings.jmod
classes/module-info.class
classes/com/greetings/Main.class
classes/org/astro/World.class

更多的命令解釋可以查看一下jmod --help,我就不多做解釋了。

java9的新功能真的挺有意思的,我這只是冰山一角,想了解更詳細的內容最好去看oracle的文檔,最后附送一張圖,是java9模塊化的小表單

最后的最后,上面命令的答案想出來了嗎,公布一下結果:

1.requires junit
2.opens com.greetings to junit
3.--module-path lib


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM