<Java 9模块化开发>读书笔记
Contents
模块描述符 module-info.java
module your.module.name {
//表示依赖某个模块. 即没有传递性的依赖.
requires other.module.name;
//表示依赖某个模块. 具有传递性的依赖
requires transitive other.module.name;
//从当前模块中导出某些包, 以提供其他模块使用
exports package.name;
//限制导出. 表示导出的 package.name 只能被 other.package 访问
exports package.name to other.package
//提供服务. 但不导出具体的实现类, 这里是 HelloServiceImpl
provides service.api.HelloService with service.api.impl.HelloServiceImpl;
//消费服务.
//表示要依赖某个模块. 其中, 并要消费该模块里面的 HelloService 服务
requires servce.api;
uses service.api.HelloService;
}
maven 中使用模块化
src
├── main
│ ├── java
│ │ ├── hello
│ │ │ └── App.java
│ │ └── module-info.java
│ └── resources
└── test
└── java
6 directories, 2 files
即在 src/main/java
目录下, 添加 module-info.java
来描述你的模块. 模块的真正名字, 可以跟目录或项目名不一致. 比如上面的 module-info.java
内容为
module hello.world {
}
App.java
内容为
package hello;
public class App {
public static void main(String[] args) {
System.out.println("Hello modular world");
}
}
那这个打包后的模块的名字就叫 hello.world
.
打包和运行
time mvn -T 1C -U clean package -Dmaven.test.skip=true
time java --module-path target/classes --module hello.world/hello.App
输出为
Hello modular world
java --module-path target/classes --module hello.world/hello.App 0.56s user 0.19s system 92% cpu 0.816 total
提供服务和消费服务
代码在 java-modular-demo , 可以直接导入使用
提供者
假设模块名为 hello.service.api
有个 HelloService
服务类. 典型情况下, 它是一个 interface
模块描述文件 module-info.java
为
module hello.service.api {
}
更好的做法是提供一个默认的实现
实现者
假设模块名为 hello.service.api.impl
. 个有 HelloServiceImpl
, 它实现了上面的 HelloService
接口.
模块描述文件 module-info.java
为
module hello.service.api.impl {
requires hello.service.api;
provides hello.service.api.HelloService with hello.service.api.impl.HelloServiceImpl;
}
消费者
假设模块名为 hello.service.api.consumer
, 有个 HelloServiceConsumer
使用服务
模块描述文件 module-info.java
为
module hello.service.api.impl {
requires hello.service.api;
uses hello.service.api.HelloService;
}
Iterable<HelloService> services = ServiceLoader.load(HelloService.class);
for (HelloService service : services) {
System.out.println("Hello " + service.xxx());
}
也可以在服务接口上, 提供一个工厂方法:(better)
public interface HelloSerivce {
yyyValue xxxxService();
static Iterable<HelloService> getServices() {
return ServiceLoader.load(HelloService.class);
}
}
这时, 要在服务接口上面模块描述文件 module-info.java
里添加
module hello.service.api {
uses HelloService;
}
否则会报如下类似错误
Exception in thread "main" java.util.ServiceConfigurationError: hello.HelloService: module hello.service does not declare `uses`
at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:588)
at java.base/java.util.ServiceLoader.checkCaller(ServiceLoader.java:574)
at java.base/java.util.ServiceLoader.<init>(ServiceLoader.java:503)
at java.base/java.util.ServiceLoader.load(ServiceLoader.java:1691)
at hello.service/hello.HelloService.getService(HelloService.java:9)
at hello.service.consumer/hello.consumer.HelloServiceConsumer.main(HelloServiceConsumer.java:7)
服务生命周期
每次调用 ServiceLoader::load
时, 都会实例化一个新的 ServiceLoader
. (意味着, 每个服务都会重新实例化一次).
创建服务实例的两个方法:
- 服务实现类必须具有无参数构造函数
- 或使用静态提供者方法(名为
provider
,public static
无参数方法. 返回类型是服务类型. 必须是返回正确类型的服务实例, 即具体化的服务类)
打包
注意, 使用 jlink
时, 它是不包括服务提供者的. 这时要手动显式添加 --add-modules
服务实现模块来打包.
上面 github 的结构中, 打包的命令如下
这样子, 并没有包含服务实现类的
jlink --module-path hello-service/target/hello-service-1.0-SNAPSHOT.jar:hello-service-impl1/target/hello-service-impl1-1.0-SNAPSHOT.jar:hello-service-impl2/target/hello-service-impl2-1.0-SNAPSHOT.jar:hello-service-consumer/target/hello-service-consumer-1.0.jar --add-modules hello.service.consumer --output image
最完整的打包命令则是这样子:
jlink --module-path hello-service/target/hello-service-1.0-SNAPSHOT.jar:hello-service-impl1/target/hello-service-impl1-1.0-SNAPSHOT.jar:hello-service-impl2/target/hello-service-impl2-1.0-SNAPSHOT.jar:hello-service-consumer/target/hello-service-consumer-1.0.jar --add-modules hello.service.consumer --add-modules hello.service --add-modules hello.service.impl1 --add-modules hello.service.impl2 --output image
然后执行
cd image
./bin/java -m hello.service.consumer/hello.consumer.HelloServiceConsumer
模块化开发
编译器标志
-Xlink:exports
标志: 如果导出类型中应该以传递方式依赖但却没有依赖的类型就会产生警告的信息.