模块描述符 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 标志: 如果导出类型中应该以传递方式依赖但却没有依赖的类型就会产生警告的信息.

带有默认实现的服务