• ServiceLoader实现原理


    在java中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口获取该接口的所有实现类却没那么容易。

    有一种比较笨的办法就是扫描classpath所有的class与jar包中的class,然后用ClassLoader加载进来,然后再判断是否是给定接口的子类。但是很显然,不会使用这种方法,代价太大。

    java本身也提供了一种方式来获取一个接口的子类,那就是使用java.util.ServiceLoader#load(java.lang.Class<S>) 方法,但是直接使用该方法也是不能获取到给定接口所有的子类的。

    需要接口的子类以配置的方式主动注册到一个接口上,才能使用ServiceLoader进行加载到子类,并且子类需要有一个无参构造方法,用于被ServiceLoader进行实例化

    下面介绍使用ServiceLoader的步骤

    1、 编写Service

    package com.mogujie.uni.sl;
    /**
     * Created by laibao
     */
    public interface Animal {
            void eat();
    }   

    2、编写实现类(注意:实现类不一定要与接口在同一个工程中,可以存在于其他的jar包中)

    package com.mogujie.uni.sl;
    /**
     * Created by laibao
     */
    public class Pig implements Animal {
        @Override
        public void eat() {
            System.out.println("Pig eating...");
        }
    }
    package com.mogujie.uni.sl;
    /**
     * Created by laibao
     */
    public class Dog implements Animal {
        @Override
        public void eat() {
            System.out.println("Dog eating...");
        }
    }

    3、 在实现类所在的工程的classpath下面的建立META-INF/services目录,该目录是固定的,一定要按照规定的名称去创建,该目录用于配置接口与实现类的映射关系 
    然后根据接口全名 在该目录创建一个文件,例如上面例子中接口全名是com.mogujie.uni.sl.Animal,那么就需要在实现类的工程中建立META-INF/services/com.mogujie.uni.sl.Animal这样一个文件,然后在该文件中配置该接口的实现类,如果该接口有多个实现类,一行写一个(以换行符分割),例如:

    com.mogujie.uni.sl.Pig
    com.mogujie.uni.sl.Dog

    4、接下来就能使用ServiceLoader的方法获取com.mogujie.uni.sl.Animal接口的所有子类了。测试类如下:

    package com.mogujie.uni;
    import com.mogujie.uni.sl.Animal;
    import java.util.Iterator;
    import java.util.ServiceLoader;
    /**
     * Created by laibao
     */
    public class TestServiceLoader {
        public static void main(String[] args) {
            ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
            Iterator<Animal> animalIterator = serviceLoader.iterator();
            while(animalIterator.hasNext()){
                Animal animal = animalIterator.next();
                animal.eat();
            }
        }
    }

    输出如下:

    Pig eating...
    Dog eating...



    ServiceLoader的原理其实很简单,就是根据给定的参数(接口)就能定位到该接口与实现类的映射配置文件的路径了,然后读取该配置文件,就能获取到该接口的子类

    下面自己实现一个CustomServiceLoader与系统的ServiceLoader具有同样的功能

    package com.mogujie.uni;
    
    import org.apache.commons.io.IOUtils;
    import java.net.URL;
    import java.util.Enumeration;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * Created by laibao
     */
    public class CustomServiceLoader {
    
        public static final String MAPPING_CONFIG_PREFIX = "META-INF/services";
    
        public static <S> List<S> loade(Class<S> service) throws Exception{
            String mappingConfigFile = MAPPING_CONFIG_PREFIX + "/" + service.getName() ;
            //由于一个接口的实现类可能存在多个jar包中的META-INF目录下,所以下面使用getResources返回一个URL数组
            Enumeration<URL> configFileUrls =  CustomServiceLoader.class.getClassLoader().getResources(mappingConfigFile);
            if(configFileUrls == null){
                return null ;
            }
            List<S> services = new LinkedList<S>();
            while(configFileUrls.hasMoreElements()){
                URL configFileUrl = configFileUrls.nextElement();
                String configContent = IOUtils.toString(configFileUrl.openStream());
                String[] serviceNames = configContent.split("
    ");
                for(String serviceName : serviceNames){
                    Class serviceClass = CustomServiceLoader.class.getClassLoader().loadClass(serviceName);
                    Object serviceInstance = serviceClass.newInstance();
                    services.add((S)serviceInstance);
                }
            }
            return services ;
        }
    
    }

    测试类如下:

    package com.mogujie.uni;
    import com.mogujie.uni.sl.Animal;
    import java.util.List;
    /**
     * Created by laibao
     */
    public class CustomServiceLoaderTest {
        public static void main(String[] args) throws Exception {
            List<Animal> animals = CustomServiceLoader.loade(Animal.class);
            for (Animal animal : animals){
                animal.eat();
            }
        }
    }

    输出:

    Pig eating...
    Dog eating...


    java系统定义的ServiceLoader与我们自定义的CustomServiceLoader的loade方法,它们的返回值类型是不一样的,ServiceLoader的loade方法返回的是ServiceLoader对象,ServiceLoader对象实现了Iterable接口,通过ServiceLoader的成员方法iterator();就能遍历所有的服务实例,而我们自定义的CustomServiceLoader的load方法返回的是一个List对象,直接将所有的服务实例封装在一个集合里面返回了。 
    系统的ServiceLoader通过返回一个Iterator对象能够做到对服务实例的懒加载 只有当调用iterator.next()方法时才会实例化下一个服务实例,只有需要使用的时候才进行实例化,具体实现读者可以去阅读源码进行研究,这也是其设计的亮点之一。

  • 相关阅读:
    太白老师 day06 编码 encode decode
    太白老师day6 1.代码块 2.is==id 3.小数据池
    MySQL 基本语法(1.表字段操作,2表记录管理 3.运算符管理4.SQL查询 5.约束6.索引
    List 接口常用子类及其特点
    Java 集合框架
    Java 常用工具类之基本对象包装类
    Java 常用工具类之 String 类
    Java 多线程间通信
    Java 多线程通信之多生产者/多消费者
    Java 之多线程通信(等待/唤醒)
  • 原文地址:https://www.cnblogs.com/coprince/p/8855829.html
Copyright © 2020-2023  润新知