• ServiceLoader详解


    系统中用到了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。

    参考:Java ServiceLoader使用和解析

     
     
  • 相关阅读:
    Java静态方法中使用注入类
    Java FTP辅助类
    Java SFTP辅助类
    MyBatis学习总结——批量查询
    MyBatis学习总结—实现关联表查询
    Redis集群搭建与简单使用
    SQL管理工具
    MySQL锁机制
    MySQL权限管理
    yii框架下使用redis
  • 原文地址:https://www.cnblogs.com/aspirant/p/10616704.html
Copyright © 2020-2023  润新知