lostars
发布于 2018-01-05 / 2007 阅读
0

JAVA 9 新特性 — MODULE SYSTEM 之 SERVICE

上一篇 介绍了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(() -&gt; 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] 这样一个简易的服务提供就完成了。

服务调用者无需关心具体服务实现,接口只需要将自己暴露(导出)即可,服务提供者对外界都是不可见的。

服务提供者的实例化方式:

  1. 提供者的无参构造方法。
  2. 提供者提供一个公共静态provider方法,返回接口或者是接口的实现。

上面两种方式优先第二种方式。

参考文档:
https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html