模块描述符 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