java9 模块化 jigsaw

本贴最后更新于 2294 天前,其中的信息可能已经时移世改
java9并没有在语言层面做出很多改变,而是致力于一些新特性,如模块化,其核心就是解决历史遗留问题,为以后的jar包森林理清道路。模块化是一个很大的命题,就不讲那么细致了,关于java9的特性也有很多书籍可供参考,如<br>[《Java 9 Revealed: For Early Adoption and Migration》][1],[《Java 9 Modularity》][2],[还有很多很多(点击查看)][3]。<br>

模块化是一个很大的特性,也是一个很大的话题。模块化作为一个未来的的趋势,我们可能会关心项目的迁移,对接OSGI或者微服务等等,本文只是简单阐述下模块化解决的基本问题和模块化的一些用法。
## 模块化

两个问题,臃肿的jar包和混乱的jar包依赖关系。如果我们没被现在的jar包系统搞疯,首先应该感谢IDE和maven,gradle之类的版本控制软件。你可能见过这个大家伙,60多m的jar包,运行一个`Hello World`程序,也需要这么一个庞然大物支撑。<br>
> ![nothing][4]<br>

再或者,我们依赖一个大的工具包,但我们可能只需要其中几个类的功能。之前的jar包机制是很僵硬的,我们需要更灵活更轻便条理更清晰的环境,从化繁为简的角度来讲,我们需要模块化。<br>

不知道你经历过jar hell吗,可以参考下图:<br>
![此处输入图片的描述][5]<br>
jar包之间的依赖可谓错综复杂,jar包之间交叉,缠绕,冲突,旋转,跳跃,我觉得没有人会想理清项目中依赖的这一个jar包与其他Jar包之间千丝万缕的联系。不看依赖,便是晴天,但是我们有时候真的需要理清楚,因为我们在部署的时候可能会产生冲突,会报各种异常。我们理清楚了,我们对这个jar包理解更深了,我们变强了,我们秃了。我们需要更清晰的图像来描述依赖关系,我们不能任由jar缠绕旋转,我们应该让他们更守规矩,让依赖图像清楚并且有层次,我们需要模块化。<br>

其实我们不需要理解模块化的字面意义,我们需要知道我们面临的困难和要解决的问题,jdk9发布的解决这些问题的新特性的名称叫做模块化。

## 漫长的模块化

在使用模块化之前,我需要让你明白一些事情,模块化不是一蹴而就,java平台做了表率,从内部进行了模块化改造。我们先来看一下传统的jdk7的API:<br>
![此处输入图片的描述][6]<br>
很熟悉的api形式。<br>
再来看一看java9的api:<br>
![此处输入图片的描述][7]<br>
点进`java.activation`<br>
![此处输入图片的描述][8]<br>
我们还可以看到模块的图像,Java9不仅为我们提供了模块化的手段,整个java9平台自身,也已经被模块化改造了。整个java生态彻底改造成模块化是需要时间的,从jdk7时提出模块化概念,到整个模块化版本发布,经历了很长时间。把原来的第三方jar包彻底改造成模块化jar包会花费很多功夫,我们能够使用jdk的模块化包,不代表我们能够使用整个Java生态中所有的模块化包,这需要开发者对自己的jar包进行调整。<br>
- 模块化为我们梳理出了逻辑清晰的jar包系统。
- 模块化需要额外的操作,处理相同的代码,我们需要理清代码之间的关系,对内容模块化,但IDE提供了一些便捷的功能。
- 拥抱彻底的模块化生态需要时间,很多重量级的框架和jar包适应模块化是需要我们等待的,但是有自动化模块可以帮助我们构建模块化项目。


## 简单入门模块化
在`$JAVA_HOME/bin`中的Java工具也为模块化提供了很多新功能,就不一一详细介绍了,感兴趣的话可以参考[Oracle的介绍][9]。下面的例子主要是参考[openJDK模块化的快速入门][10]和[GitHub上的一个项目][11],很好的例子,我也就不做太多更改了,会简要叙述下例子中的新事物。<br>

首先要下载JDK9,可以去官网下载,我是用Linux平台演示的代码。
###  先来看第一个例子,输出`Greetings!`<br>
 先来看一看整个项目的目录结构:<br>
```shell
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`主类文件。让我们看一看这两个文件的内容。
```shell
$ cat src/com.greetings/module-info.java
    module com.greetings { }
```
模块文件中没有内容,因为这个`Main.java`中没有依赖别的模块的内容,也没有打算向外界暴露`Main.java`的内容,所以内容是空的。
```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!");
    }
}
```
然后,我们把这两个文件编译运行。
```shell
javac -d mods/com.greetings \
        src/com.greetings/module-info.java \
        src/com.greetings/com/greetings/Main.java
```
javac -d 指定放置生成的类文件的位置
```shell
java --module-path mods \
-m com.greetings/com.greetings.Main 
#或者
java --module-path mods \
--module com.greetings/com.greetings.Main
```
就可以运行了,我们会发现屏幕上打印出了Greetings!<br>
其中 --module-path <模块路径>...用 : 分隔的目录列表, 每个目录都是一个包含模块的目录。<br>
-m或--module是一样的,用于指定要解析的初始化模块名称,如果不指定模块名称,可以指定要执行的主类名称


###  再来看第二个例子,Greetings world!<br>

我们先来看一下目录结构:<br>
```shell
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
```
再来看看代码内容:<br>
```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`模块暴露出去,可供其他模块依赖。
```shell
#我们先编译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模块
```shell
 javac -d mods/org.astro \
> src/org.astro/module-info.java src/org.astro/org/astro/World.java
```
然后编译一下com.greeetings
```shell
javac --module-path mods -d mods/com.greetings src/com.greetings/com/greetings/Main.java src/com.greetings/module-info.java
```
--module-path(或-p) 用来指定应用的模块的位置。

运行一下
```shell
java --module-path mods -m com.greetings/com.greetings.Main
//Greetings world!
```
这个例子中要注意`exports`和`requires`的用法,`exports`对外界暴露内容,`requires`引入外界功能,如果没有引入,或者引入不存在的module编译无法通过。
```shell
#如果没有声明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
```
###  多模块的编译

可以一次编译多个模块<br>
我们先找出所有.java文件
```shell
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`文件。<br>
我们把已经编译好的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

如何使用模块系统提供服务呢,让我们来看看。<br>
首先查看一下工程的结构:<br>
```
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](https://note.youdao.com/yws/api/personal/file/E9BD6F6D818D4930A203E78EF97C2894?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f)
com.scoket中还有个`NetworkSocketProvider.java`,其实就是用来创建socket的一个工厂。我们再来看看他们的代码:<br>
先来看看org.socket模块:<br>
```
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`对象并返回。<br>
再来看看NetworkSocketProvider:<br>
```
package com.socket.spi;

import com.socket.NetworkSocket;

public abstract class NetworkSocketProvider {
    protected NetworkSocketProvider() {}

    public abstract NetworkSocket openNetworkSocket();
}
```
一个抽象类,`openNetworkSocket()`抽象方法由子类实现。<br>
module-info.java<br>
```
module com.socket {
        exports com.socket;
        exports com.socket.spi;
        uses com.socket.spi.NetworkSocketProvider;
}

```
我想上面两个exports你应该能够理解其意义,但是uses是什么意思呢。为了使用服务,它的提供者需要被发现和加载。java.util.ServiceLoader类来发现和加载服务。发现和加载服务提供者的模块必须在其声明中包含一个uses语句,该语句语法如下:<br>`uses <service-interface>`;`<service-interface>`是服务接口的名称,它是Java接口名称,类名称或注解类型名称。如果一个模块使用`ServiceLoader<S>`类加载名为S的服务接口的服务提供者的实例,则模块声明必须包含以`uses S`。总而言之,就是你如果是个服务接口,你需要服务提供的类实现你的接口,你就需要声明`uses`。<br>

那我们在看一看服务提供者类,`org.fastsocket`:<br>
```
//继承了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;
}

```
其中有个`provides`和`with`,意思就是为`NetworkSocketProvider`提供一个实现类`org.fastsocket.FastNetworkSocketProvider`。<br>
再来快速看一下客户端代码,我们运行一下整个服务系统。
```
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.socket`,`Main.java`打印了提供服务的类名。<br>
我们先试着编译服务接口代码和客户端代码:<br>

```
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`关键字,意味着这个模块需要实现类,但是在编译时他不会寻找需要的实现类模块。<br>
运行一下看看。<br>
```
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)
```
报了一个运行时异常,意味着对于服务的具体实现是在运行是寻找的。<br>
我们把实现类编译一下,然后运行:<br>
```
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的热插拔。通过模块系统,我们实现了对服务接口和服务提供者的解耦,我们确保服务提供者在调用服务之前能够编译到模块中即可。

###  模块补丁<br>

什么是模块补丁呢,假如我们的项目依赖了一个A模块,但是觉得需要自定义A模块中某些类的功能,那可以修改这个类,然后把修改后的类打到依赖的A模块中。这样,我们的项目就可以使用拥有自定义类的模块了。下面以`ConcurrentHashMap`为例,`ConcurrentHashMap`是`java.base`模块中的一个类,我们将自定义的`ConcurrentHashMap`类替换`java.base`中`ConcurrentHashMap`。<br>
先来看一看整个工程的结构:<br>
```
├── 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`中的`ConcurrentHashMap`,`java.base`模块里包含了我们重写的`ConcurrentHashMap`类,而且注意一点,这个模块中没有`module-info.java`描述文件。再来看看模块的内容:<br>
```
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.lang`和`java.util`等等基础包。`Main.java`中的内容也很简单,放入`ConcurrentHashMap`中几个元素,然后打印。<br>

再来看看我们重写的`ConcurrentHashMap`的内容,可以只看`put()`方法,`containsKey()`方法和`toString()`方法:<br>
```
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`模块。<br>
```
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`类。<br>
试一试对`java.base`打上补丁:<br>
```
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`可以看到这个命令的解释<br>
> --patch-module <模块>=<文件>(:<文件>)*,使用 JAR文件或目录中的类和资源覆盖<br>

所以呢,我们把`java.base`中的`ConcurrentHashMap`类给覆盖了,在运行时,也有一个`--patch-module`,其实和上面的解释是一样的,运行时使用`mypatches/java.base`文件覆盖`java.base`文件,然后得到了想要的结果。<br>

###  自动模块化

当我们已经开始模块化的工程,但是需要依赖一些非模块化的jar包,这些库的开发人员并没有及时更新库使其适应模块化,他们缺少模块化的描述符,所以我们需要自动模块化--将非模块化的jar包转化成模块化jar。以`junit`为例,我们先来看看`junit`jar包的描述信息。
```
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包的依赖。<br>
```
jdeps -s lib/junit-4.12.jar 
junit-4.12.jar -> java.base
junit-4.12.jar -> java.management
junit-4.12.jar -> 找不到
```
-s只是查看以来的概览,有些依赖是不确定的,那我们看看丢失了哪些依赖:<br>
```
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`需要哪些依赖:<br>
```
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文件的内容:<br>
```
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的目录结构,然后再看看四个文件的内容:<br>
```
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`文件中设置了一个小问题,可以试着填一填,答案在文章末尾。大体内容我们知道了,可是我们应该怎么编译呢:<br>
```
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
```
答案也在文章末尾,可以自己试着编译一下。我们来运行一下:<br>
```
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`类:<br>
```
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
```
运行一下:<br>
```
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**有什么用呢,其实很简单,我们可以通过jlink构造自己的jre,前文提到rt.jar包很臃肿,jlink可以帮助我们构建精巧的运行环境。那我们先来看看整个项目的内容,内容很简单,和第二个小测试一样,可以快速浏览一下,主要是为了练习使用jlink工具的使用:<br>
```
//结构目录
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";
        }
}
```
那我们编译一下:<br>
```
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包:<br>

```
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!<br>
```
 jlink --module-path "${JAVA_HOME}"/jmods:mlib \
        --add-modules com.greetings \
        --output executable
```
完成!我们把执行文件输出到了excutable文件夹,如果对这些命令选项不太理解,可以通过`jlink --help`来查看命令解释。我们现在已经可以执行了:<br>
```

./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包不一样。<br>
直接在上个练习目录中试用命令就好。
```
jmod create \
              --class-path mods/com.greetings:mods/org.astro \
              greetings.jmod
```
然后查看一下:<br>
```
jmod list greetings.jmod
classes/module-info.class
classes/com/greetings/Main.class
classes/org/astro/World.class
```
更多的命令解释可以查看一下jmod --help,我就不多做解释了。

java9的新功能真的挺有意思的,我这只是冰山一角,想了解更详细的内容最好去看oracle的文档,最后附送一张图,[是java9模块化的小表单](http://files.zeroturnaround.com/pdf/RebelLabs-Java-9-modules-cheat-sheet.pdf)。

最后的最后,上面命令的答案想出来了吗,公布一下结果:<br>
1.requires junit
2.opens com.greetings to junit
3.--module-path lib

  [1]: http://www.java1234.com/a/javabook/javabase/2017/0426/8008.html
  [2]: http://shop.oreilly.com/product/0636920049494.do
  [3]: https://ssearch.oreilly.com/?q=java%209
  [4]: https://note.youdao.com/yws/api/personal/file/E077D0E4738D4EAD97D09464916F280F?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f
  [5]: https://note.youdao.com/yws/api/personal/file/D70672E6C96949BAA13DB5D75297E040?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f
  [6]: https://note.youdao.com/yws/api/personal/file/EAD63F4001004801A0E664BF6256DFF2?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f
  [7]: https://note.youdao.com/yws/api/personal/file/84E9DB852EE34B57A95349B50923B799?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f
  [8]: https://note.youdao.com/yws/api/personal/file/02945C2B71D64AC3B794D5D1FB98023B?method=download&shareKey=952006455eadac7758f7ae04b1d59c7f
  [9]: https://docs.oracle.com/javase/9/tools/tools-and-command-reference.htm#JSWOR596
  [10]: http://openjdk.java.net/projects/jigsaw/quick-start
  [11]: https://github.com/AdoptOpenJDK/jdk9-jigsaw
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3186 引用 • 8212 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 164 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    287 引用 • 4484 回帖 • 667 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    21 引用 • 140 回帖 • 4 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖 • 2 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 48 关注
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    122 引用 • 73 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    942 引用 • 1459 回帖 • 31 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 506 关注
  • 导航

    各种网址链接、内容导航。

    39 引用 • 170 回帖 • 5 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖 • 2 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1705 回帖
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    565 引用 • 3532 回帖
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 32 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 70 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 3 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    221 引用 • 473 回帖
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 19 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 337 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    124 引用 • 169 回帖
  • 创业

    你比 99% 的人都优秀么?

    84 引用 • 1399 回帖
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖 • 1 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 401 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 354 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖