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程序,也需要这么一个庞然大物支撑。

再或者,我们依赖一个大的工具包,但我们可能只需要其中几个类的功能。之前的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!

这个例子中要注意exports和requires的用法,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

有点多,我们先理一理其中的关系



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

其中有个provides和with,意思就是为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.socket,Main.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为例,ConcurrentHashMap是java.base模块中的一个类,我们将自定义的ConcurrentHashMap类替换java.base中ConcurrentHashMap。

先来看一看整个工程的结构:

├── 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描述文件。再来看看模块的内容:

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中几个元素,然后打印。

再来看看我们重写的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有什么用呢,其实很简单,我们可以通过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

java9 模块化 jigsaw的更多相关文章

  1. JAVA9模块化详解(一)——模块化的定义

    JAVA9模块化详解 前言 java9已经出来有一段时间了,今天向大家介绍一下java9的一个重要特性--模块化.模块化系统的主要目的如下: 更可靠的配置,通过制定明确的类的依赖关系代替以前那种易错的 ...

  2. JAVA9模块化详解(二)——模块的使用

    JAVA9模块化详解(二)--模块的使用 二.模块的使用 各自的模块可以在模块工件中定义,要么就是在编译期或者运行期嵌入的环境中.为了提供可靠的配置和强健的封装性,在分块的模块系统中利用他们,必须确定 ...

  3. Java9模块化(Jigsaw)初识

    Java9经历了多次跳票,终于要在9月份正式发布,原计划Jigsaw在Java7就有的,也终于在Java9里面提供了,简单总结下. 对比 Java9 以前 上面2个图分别对应的分别是JDK8/9的目录 ...

  4. Java9 modules (Jigsaw)模块化迁移

    要点 通过模块化的方式开发应用程序,实现更好的设计,如关注点分离和封装性. 通过Java平台模块化系统(JPMS),开发者可以定义他们的应用程序模块,决定其他模块如何调用他们的模块,以及他们的模块如何 ...

  5. Java 9 模块化(Modularity)

    JDK9的发布一直在推迟,终于在2017年9月21日发布了.下面是JDK9的几个下载地址: JDK9.0.1 Windows-x64下载地址 Oracle Java 官网下载地址 OpenJDK 9官 ...

  6. Java11实战:模块化的 Netty RPC 服务项目

    Java11实战:模块化的 Netty RPC 服务项目 作者:枫叶lhz链接:https://www.jianshu.com/p/19b81178d8c1來源:简书简书著作权归作者所有,任何形式的转 ...

  7. java9模块不可见问题

    问题描述 jdk.internal.reflect包不可见 问题原因 java9模块化之后,java.base只把jdk.internal.reflect暴露给了少数几个内部包而没有向当前模块暴露. ...

  8. 虚拟机--第一章走进java--(抄书)

    这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第一章走近java 世界上并没有 ...

  9. 《Java程序员修炼之道》

    原子类:java.util.concurrent.atomic 线程锁:java.util.concurrent.locks 对付死锁:boolean acquired = lock.tryLock( ...

随机推荐

  1. 转:Linux常用命令总结

    学习linux也有一阵子了,现总结一些常用的linux操作命令,方便大家查找1. cd命令这个命令是最基本的也是最常用的.它用于切换当前目录,可以是绝对路径,也可以是相对路径.例:cd /root/h ...

  2. mongoDB 的一般使用

    理解 mongodb 也是nosql 的一种.他的数据存储类型是一种和json格式比较像的数据类型,可以看作就是json. mongodb 里的数据库都是一个单独的库.一般需要用的库都会设置自己的us ...

  3. Java第三天【变量、常量、数据类型】

    学习Java第三天!加油!请帖友看看有和不足的地方,和在下说一下,谢谢! 变量 变量(variable): 1.变量的本质就是代表"可操作的存储空间",空间位置是确定的,蛋里面放置 ...

  4. Linux&C网络编程————“聊天室”

    从上周到现在一直在完成最后的项目,自己的聊天室,所以博客就没怎么跟了,今天晚上自己的聊天室基本实现,让学长检查了,也有好些bug,自己还算满意,主要实现的功能有: 登录注册 附近的人(服务器端全部在线 ...

  5. 为 Android 编译并集成 FFmpeg 的尝试与踩坑

    前言与环境说明 随着 FFmpeg.NDK 与 Android Studio 的不断迭代,本文可能也会像我参考过的过期文章一样失效(很遗憾),但希望本文中提到的问题排查以及步骤说明能够帮到你,如果发现 ...

  6. 【数据结构&算法】10-串基础&KMP算法源码

    目录 前言 串的定义 串的比较 串的抽象类型数据 串与线性表的比较 串的数据 串的存储结构 串的顺序存储结构 串的链式存储结构 朴素的模式匹配算法 模式匹配的定义 朴素的匹配方法(BRUTE FORC ...

  7. spring security 之自定义表单登录源码跟踪

    ​ 上一节我们跟踪了security的默认登录页的源码,可以参考这里:https://www.cnblogs.com/process-h/p/15522267.html 这节我们来看看如何自定义单表认 ...

  8. RISCV 入门 (学习笔记)

    文章目录 1. risv 相关背景 1.1 arm 授权费 1.2 riscv 发展历史 1.3 riscv 风险 2. 指令集 2.1 可配置的通用寄存器组 2.2 规整的指令编码 2.3 简洁的存 ...

  9. Buildroot 用户手册 (中文)

    文章目录 I. Getting started 1. About Buildroot 2. System requirements 2.1. Mandatory packages 2.2. Optio ...

  10. 一行导出所有任意微软SQL server数据脚本-基于Python的微软官方mssql-scripter工具使用全讲解

    文章标题: 一行导出所有任意微软SQL serer数据脚本-基于Python的微软官方mssql-scripter工具使用全讲解 关键字 : mssql-scripter,SQL Server 文章分 ...