上一篇 介绍了Java 9模块系统的一些基本信息,这一篇介绍模块化服务的实现、加载和使用。
普通java interface的使用基本是获取实现类或者在实例化接口的时候自己实现。获取实现类这种方式对服务的实现者有一定的入侵,如果在模块化中实现者就必须导出模块,这样项目之间的耦合其实是变强了。而实例化接口时候自己实现又显得太过麻烦。
Java 9中加载服务服务的方式是使用ServiceLoader来加载,这个其实在java 6中就已经有了,但是在9中重新进行了修改。
我用一个很简单的demo来具体介绍,定义一个提供获取整数约数的接口。
public interface ApproximateGetter {
/**
* 获取约数
*
* @param number
* @return 返回约数数组
*/
List<Long> getApproximateNumbers(long number);
}
从java8开始允许接口中定义静态方法,那么我们定义一个获取接口实例的方法:
static ApproximateGetter newInstance() {
return ServiceLoader.load(ApproximateGetter.class).findFirst().orElseThrow(() -> new RuntimeException("no provider available"));
}
事实上java9中的很多接口都是以这种方式来对外提供接口实例的,这样做的好处就是接口的实例模块无需导出(官方也是不推荐导出),服务调用者无需关注服务具体的实现,只需要能够获取到我需要的方法即可。
上面的代码中通过ServiceLoader::load方法来加载服务,load方法有多个定义,具体可以查阅文末附上的参考链接。然后从可用的服务提供者中获取第一个返回否则的话抛出异常。
然后是实现这个接口(定义在另外一个模块中):
public class ApproximateEffectiveProvider implements ApproximateGetter {
private static final String PROVIDER_NAME = "effective provider";
@Override
public List<Long> getApproximateNumbers(long number) {
if (number <= 0) {
return new ArrayList<>();
}
List ret = new ArrayList<>();
for (long i = 1; i < number/2; i++) {
if (number%i == 0) {
ret.add(i);
}
}
return ret;
}
}
以上操作做完依旧是无法加载到服务的,还有几个非常重要的地方:
ServiceLoader::load方式加载服务首先需要在接口定义的模块中用uses
关键字声明接口,表明我是作为一个接口暴露出来的,这样load方法才会发现接口。
module me.minei.approximate {
exports me.minei.approximate;
uses me.minei.approximate.ApproximateGetter;
}
其实就是在load调用所在的模块必须声明对某个服务的使用,不然模块系统无法找到相应的接口。
然后是服务提供者(服务实现)提供服务可以通过两种方式:
- 实现者定义在模块中,使用provides…with来提供服务:
module me.minei.approximate.effective { requires me.minei.approximate; provides me.minei.approximate.ApproximateGetter with me.minei.approximate.effective.ApproximateEffectiveProvider; }
多个实现用逗号隔开即可,表明我是通过ApproximateEffectiveProvider来实现ApproximateGetter,系统就会发现该提供者。
- 定义在classpath中,这种方式就是非模块化的实现方式,在classpath META-INF/services文件夹下配置提供者,文件名称用接口完整包路径命名,内容则是服务实现者的完整包路径,文件必须utf-8编码。
以上两种方式其实也是部署服务的两种方式,这里主要介绍第一种方式。
建立测试类测试:
public class ApproximateTest {
public static void main(String[] args) {
System.out.println(ApproximateGetter.availableProvider());
ApproximateGetter getter = ApproximateGetter.newInstance();
System.out.println(getter.getApproximateNumbers(3333));
}
}
输出: [1, 3, 11, 33, 101, 303, 1111] 这样一个简易的服务提供就完成了。
服务调用者无需关心具体服务实现,接口只需要将自己暴露(导出)即可,服务提供者对外界都是不可见的。
服务提供者的实例化方式:
- 提供者的无参构造方法。
- 提供者提供一个公共静态provider方法,返回接口或者是接口的实现。
上面两种方式优先第二种方式。
参考文档:
https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html