当前位置: 首页 > 编程笔记 >

详谈ServiceLoader实现原理

夔庆
2023-03-14
本文向大家介绍详谈ServiceLoader实现原理,包括了详谈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("\n");
      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()方法时才会实例化下一个服务实例,只有需要使用的时候才进行实例化,具体实现读者可以去阅读源码进行研究,这也是其设计的亮点之一。

以上这篇详谈ServiceLoader实现原理就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持小牛知识库。

 类似资料:
  • 问题内容: 我尝试使用Java ServiceLoader查找实现特定接口的所有类,如下所示: 不幸的是,当我在调试模式下运行Eclipse时,ServiceLoader找不到任何类。我觉得我错过了一个琐碎的事情… 问题答案: 无法做到。 为了将类公开为可以被发现的服务,您需要将其名称放入提供程序配置文件中,如使用Java平台创建可扩展应用程序中所述 。 没有内置的方法可以找到实现特定接口的所有类

  • 本文向大家介绍ReentrantLock实现原理详解,包括了ReentrantLock实现原理详解的使用技巧和注意事项,需要的朋友参考一下 以下是本篇文章的大纲 1 synchronized和lock     1.1 synchronized的局限性     1.2 Lock简介 2 AQS 3 lock()与unlock()实现原理     3.1 基础知识     3.2 内部结构     3

  • 本文向大家介绍浅谈MyBatis通用Mapper实现原理,包括了浅谈MyBatis通用Mapper实现原理的使用技巧和注意事项,需要的朋友参考一下 本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。 基本原理 通用 Mapper 提供了一些通用的方法,这些通用方法是以接口的形式提供的,例如。 接口和方法都使用了泛型,使用该通用方法的接口需要指定泛型的类型。通过 Jav

  • 本文向大家介绍详解 Java HashMap 实现原理,包括了详解 Java HashMap 实现原理的使用技巧和注意事项,需要的朋友参考一下 HashMap 是 Java 中最常见数据结构之一,它能够在 O(1) 时间复杂度存储键值对和根据键值读取值操作。本文将分析其内部实现原理(基于 jdk1.8.0_231)。  数据结构 HashMap 是基于哈希值的一种映射,所谓映射,即可以根据 key

  • 本文向大家介绍详解@ConfigurationProperties实现原理与实战,包括了详解@ConfigurationProperties实现原理与实战的使用技巧和注意事项,需要的朋友参考一下 在SpringBoot中,当需要获取到配置文件数据时,除了可以用Spring自带的@Value注解外,SpringBoot提供了一种更加方便的方式:@ConfigurationProperties。只要在

  • 本文向大家介绍浅谈C++中虚函数实现原理揭秘,包括了浅谈C++中虚函数实现原理揭秘的使用技巧和注意事项,需要的朋友参考一下 编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟。      编译器对每个包含虚函数的类创建一个表(称为V TA B L E)。在V TA B L E中,编译器放置特定类的虚函数地址。在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(