系统中用到了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" 
         ; 
             
         } 
        } 
         |  
     
|  
        
         1 
         
       
         2 
          |  
       
       com.impl.HDFSService 
        com.impl.LocalService 
         |  
     
|  
        
         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 
         |  
     
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。
