系统中用到了ServiceLoader,查了一下:

ServiceLoader与ClassLoader是Java​中2个即相互区别又相互联系的加载器.JVM利用ClassLoader将类载入内存,这是一个类声明周期的第一步(一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况)。详情请参阅:详解Java类的生命周期

那ServiceLoader又是什么呢?ServiceLoader:一个简单的服务提供者加载设施。服务 是一个熟知的接口和类(通常为抽象类)集合。服务提供者 是服务的特定实现。提供者中的类通常实现接口,并子类化在服务本身中定义的子类。服务提供者可以以扩展的形式安装在 Java 平台的实现中,也就是将 jar 文件放入任意常用的扩展目录中。也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用。……唯一强制要求的是,提供者类必须具有不带参数的构造方法,以便它们可以在加载中被实例化。

通过在资源目录META-INF/services中放置提供者配置文件 来标识服务提供者。文件名称是服务类型的完全限定二进制名称。该文件包含一个具体提供者类的完全限定二进制名称列表,每行一个。忽略各名称周围的空格、制表符和空行。注释字符为'#'('\u0023', NUMBER SIGN);忽略每行第一个注释字符后面的所有字符。文件必须使用 UTF-8 编码。

以延迟方式查找和实例化提供者,也就是说根据需要进行。服务加载器维护到目前为止已经加载的提供者缓存。每次调用 iterator​ 方法返回一个迭代器,它首先按照实例化顺序生成缓存的所有元素,然后以延迟方式查找和实例化所有剩余的提供者,依次将每个提供者添加到缓存。可以通过 reload 方法清除缓存。

……

以上来源于Java API里的说明,也许说的很专业,让我们有点晕头转向,我们可以简单的认为:ServiceLoader也像ClassLoader一样,能装载类文件,但是使用时有区别,具体区别如下:(1) ServiceLoader装载的是一系列有某种共同特征的实现类,而ClassLoader是个万能加载器;(2)ServiceLoader装载时需要特殊的配置,使用时也与ClassLoader有所区别;(3)ServiceLoader还实现了Iterator接口。[如有错误或不到的地方敬请指出,互相学习:)]

下面是关于ServiceLoader的简单的例子,仅供参考

(1)基础服务:IService


1 2 3 4 5

​package ​​​​com.service;​​​​public​​​​ interface​​​​IService {​​​​​​​​String sayHello();​​​​​​​​String getScheme();​​​​}​



(2)具体服务实现1:HDFSService


1 2 3 4 5 6 7 8 9 10 11 12

​package ​​​​com.impl;​​​​import ​​​​com.service.IService;​​​​public​​​​ class ​​​​HDFSService ​​​​implements ​​​​IService {​​​​​​​​@Override​​​​​​​​public ​​​​String sayHello() {​​​​​​​​return ​​​​"Hello HDFSService"​​​​;​​​​​​​​}​​​​​​​​@Override​​​​​​​​public ​​​​String getScheme() {​​​​​​​​return ​​​​"hdfs"​​​​;​​​​​​​​}​​​​}​



(3)具体服务实现2:LocalService



1 2 3 4 5 6 7 8 9 10 11 12

​package ​​​​com.impl;​​​​import ​​​​com.service.IService;​​​​public​​​​ class​​​​LocalService ​​​​implements ​​​​IService {​​​​​​​​@Override​​​​​​​​public ​​​​String sayHello() {​​​​​​​​return ​​​​"Hello LocalService"​​​​;​​​​​​​​}​​​​​​​​@Override​​​​​​​​public ​​​​String getScheme() {​​​​​​​​return ​​​​"local"​​​​;​​​​​​​​}​​​​}​

(4)配置:META-INF/services/com.service.IService




1 2

​com.impl.HDFSService​​​​com.impl.LocalService​

(5)测试类

1 2 3 4 5 6 7 8 9 10 11

​package ​​​​com.test;​​​​import ​​​​java.util.ServiceLoader;​​​​import ​​​​com.service.IService;​​​​public​​​​ class​​​​Test {​​​​​​​​public ​​​​static​​​​void​​​​main(String[] args) {​​​​​​​​ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.​​​​class​​​​);​​​​​​​​for​​​​(IService service : serviceLoader) {​​​​​​​​System.out.println(service.getScheme()+​​​​"="​​​​+service.sayHello());​​​​​​​​}​​​​​​​​}​​​​}​



结果:

hdfs=Hello HDFSService

local=Hello LocalService

可以看到ServiceLoader可以根据IService把定义的两个实现类找出来,返回一个ServiceLoader的实现,而ServiceLoader实现了Iterable接口,所以可以通过ServiceLoader来遍历所有在配置文件中定义的类的实例。


ServiceLoader的应用

(1)Hadoop FileSystem

Hadoop FileSystem就是通过这个机制来根据不同文件的scheme来返回不同的FileSystem。


1 2 3 4 5 6 7 8 9 10 11

​private ​​​​static​​​​void​​​​loadFileSystems() { ​​​​​​​​synchronized​​​​(FileSystem.​​​​class​​​​){ ​​​​​​​​if​​​​(!FILE_SYSTEMS_LOADED) { ​​​​​​​​ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem.​​​​class​​​​); ​​​​​​​​for​​​​(FileSystem fs : serviceLoader) { ​​​​​​​​SERVICE_FILE_SYSTEMS.put(fs.getScheme(),fs.getClass()); ​​​​​​​​} ​​​​​​​​FILE_SYSTEMS_LOADED= ​​​​true​​​​; ​​​​​​​​} ​​​​​​​​} ​​​​}​

对应的配置文件:



1 2 3 4 5 6 7

​org.apache.hadoop.fs.LocalFileSystem ​​​​org.apache.hadoop.fs.viewfs.ViewFileSystem ​​​​org.apache.hadoop.fs.s3.S3FileSystem ​​​​org.apache.hadoop.fs.s3native.NativeS3FileSystem ​​​​org.apache.hadoop.fs.kfs.KosmosFileSystem ​​​​org.apache.hadoop.fs.ftp.FTPFileSystem ​​​​org.apache.hadoop.fs.HarFileSystem​

通过之前的测试类输出对应的scheme和class如下:

file=class org.apache.hadoop.fs.LocalFileSystem

viewfs=class org.apache.hadoop.fs.viewfs.ViewFileSystem

s3=class org.apache.hadoop.fs.s3.S3FileSystem

s3n=class org.apache.hadoop.fs.s3native.NativeS3FileSystem

kfs=class org.apache.hadoop.fs.kfs.KosmosFileSystem

ftp=class org.apache.hadoop.fs.ftp.FTPFileSystem

har=class org.apache.hadoop.fs.HarFileSystem

hdfs=class org.apache.hadoop.hdfs.DistributedFileSystem

hftp=class org.apache.hadoop.hdfs.HftpFileSystem

hsftp=class org.apache.hadoop.hdfs.HsftpFileSystem

webhdfs=class org.apache.hadoop.hdfs.web.WebHdfsFileSystem


可以看到FileSystem会把所有的FileSystem的实现都以scheme和class来cache,之后就从这个cache中取相应的值。因此,以后可以通过ServiceLoader来实现一些类似的功能,而不用依赖像Spring这样的第三方框架。


(2)责任链模式

责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

责任连模式可以使用ServiceLoader实现具体服务对象的迭代加载并处理,为了确保此模式的灵活性,建议判断逻辑通过配置文件或数据库​的方式,具体实现方式见 参考链接(2) 消灭成堆的……



一、使用场景

一般使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:

HelloService service = new HelloImpl();

如果需要动态的获取一个接口的实现类呢?全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,一般不会这么做。一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK给我们提供的TestServiceLoader 就是这种方式。

二、使用方式

在实现类的jar包的META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。

通过以下的例子来分析实现原理.

1. 新建一个接口,2个实现类。

package com.test.loader;


public interface HelloService {

public void sayHello();

}

package com.test.loader;


public class Dog implements HelloService {


@Override

public void sayHello() {

System.out.println("bark bark bark...");

}

}

package com.test.loader;


public class Sheep implements HelloService {


@Override

public void sayHello() {

System.out.println("bleat bleat bleat...");

}

}

2. 分别把接口、2个实现类打成3个jar包,放在D盘下。


3. 在Dog.jar、Sheep.jar分别加上META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。如下:


4. 使用指定的ClassLoader不包含实现类

public static void notInTheClassLoader() throws MalformedURLException {

ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar") },

TestServiceLoader.class.getClassLoader().getParent());


/* 指定的ClassLoader没有实现类,所以扫描不到META-INF/services/com.test.loader.HelloService */

ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);


Iterator<HelloService> it = helloServices.iterator();

while (it.hasNext()) {

HelloService service = it.next();

service.sayHello();

}

}

结果不会打印任何信息。

5. 指定的ClassLoader包含实现类

public static void inTheClassLoader() throws MalformedURLException {

ClassLoader serviceCL = new URLClassLoader(

new URL[] { new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },

TestServiceLoader.class.getClassLoader().getParent());


/* 实现类在指定的ClassLoader,所以可以扫描META-INF/services/com.test.loader.HelloService */

ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);


Iterator<HelloService> it = helloServices.iterator();

while (it.hasNext()) {

HelloService service = it.next();

service.sayHello();

}

}

结果如下:

bark bark bark...


bleat bleat bleat...

6. 使用指定的ClassLoader加载接口类,不指定ClassLoader加载实现类。

public static void defaultClassLoader() throws MalformedURLException, ClassNotFoundException {

ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar"),

new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },

TestServiceLoader.class.getClassLoader().getParent());


/* 默认会使用 ClassLoader.getSystemClassLoader() */

ServiceLoader<HelloService> helloServices = ServiceLoader

.load(((Class<HelloService>) (serviceCL.loadClass("com.test.loader.HelloService"))));


Iterator<HelloService> it = helloServices.iterator();

while (it.hasNext()) {

HelloService service = it.next();

service.sayHello();

}

}

结果不打印任何信息。

7. 把2个实现jar加到工程的Build Path里面,不指定ClassLoader。


public static void notSpecifyClassLoader() {

ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);


Iterator<HelloService> it = helloServices.iterator();

while (it.hasNext()) {

HelloService service = it.next();

service.sayHello();

}

}

结果如下:

bark bark bark...

bleat bleat bleat...

三、简单解析ServiceLoader

1. 构造函数,如果不指定ClassLoader或者指定的为null,则使用ClassLoader.getSystemClassLoader() ,即AppClassLoader。

private ServiceLoader(Class<S> svc, ClassLoader cl) {


service = Objects.requireNonNull(svc, "Service interface cannot be null");


loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;


acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;


reload();


}

2. 遍历有两个方法

hasNext会调用hasNextService,如果指定的ClassLoader为空(一般不会为空,构造函数会初始化),则调用ClassLoader.getSystemClassLoader().getResources,否则调用指定的ClassLoader的getResources方法,获取META-INF/services/接口的全限定名称,如META-INF/services/com.test.loader.HelloService,从这个文件中找出实现类全限定名称。

next会调用nextService,根据hasNextService获取的实现类信息,使用指定的ClassLoader进行加载和实例化。

private boolean hasNextService() {


if (nextName != null) {


return true;


}


if (configs == null) {


try {


String fullName = PREFIX + service.getName();


if (loader == null)


configs = ClassLoader.getSystemResources(fullName);


else


configs = loader.getResources(fullName);


} catch (IOException x) {


fail(service, "Error locating configuration files", x);


}


}


while ((pending == null) || !pending.hasNext()) {


if (!configs.hasMoreElements()) {


return false;


}


pending = parse(service, configs.nextElement());


}


nextName = pending.next();


return true;


}




private S nextService() {


if (!hasNextService())


throw new NoSuchElementException();


String cn = nextName;


nextName = null;


Class<?> c = null;


try {


c = Class.forName(cn, false, loader);


} catch (ClassNotFoundException x) {


fail(service,


"Provider " + cn + " not found");


}


if (!service.isAssignableFrom(c)) {


fail(service,


"Provider " + cn + " not a subtype");


}


try {


S p = service.cast(c.newInstance());


providers.put(cn, p);


return p;


} catch (Throwable x) {


fail(service,


"Provider " + cn + " could not be instantiated",


x);


}


throw new Error(); // This cannot happen


}

注:以上代码基于JDK1.8.0_144。