在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实现原理的更多相关文章

  1. Java SPI机制:ServiceLoader实现原理及应用剖析

    一.背景 SPI,全称Service Provider Interfaces,服务提供接口.是Java提供的一套供第三方实现或扩展使用的技术体系.主要通过解耦服务具体实现以及服务使用,使得程序的可扩展 ...

  2. 【java编程】ServiceLoader使用看这一篇就够了

    转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...

  3. JDBC【3】-- SPI技术使用以及在数据库连接中的使用

    目录 1.SPI是什么? 2.如何使用SPI来提供自定义服务? 2.1 DBInterface定义接口 2.2 模拟Mysql实现驱动 2.3 模拟SqlServer实现驱动 2.4 模拟用户使用不同 ...

  4. JAVA SPI(Service Provider Interface)原理、设计及源码解析(其一)

    背景 团队内部轮流技术分享,其他人都是分享源码,我每次都是设计和架构,感觉自己太特立独行.这次我要合群点,分享点源码. 概念 Service Provider Interface:服务提供方接口.是一 ...

  5. ServiceLoader详解

    系统中用到了ServiceLoader,查了一下: ServiceLoader与ClassLoader是Java中2个即相互区别又相互联系的加载器.JVM利用ClassLoader将类载入内存,这是一 ...

  6. SpringBoot初体验及原理解析

    一.前言 ​ 上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对“约定大于配置(Conv ...

  7. Dubbo原理和源码解析之“微内核+插件”机制

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  8. dubbo实现原理之SPI简介

    dubbo采用微内核+插件体系,设计优雅,扩展性很强.微内核+插件体系是如何实现的呢?想必大家都知道SPI(service provider interface)机制.这种机制的原理是假如我们定义了服 ...

  9. Dubbo学习笔记6:Dubbo增强SPI与SPI中扩展点自动包装的实现原理

    在Dubbo整体架构分析中介绍了Dubbo中除了Service和Config层为API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,也就是扩展性比较强,这也是Dubbo比较好的 ...

随机推荐

  1. 使用 phpStudy + VSCODE 进行 PHP 断点调试

    使用 phpStudy + VSCODE 进行 PHP 断点调试 自己摸索过程有点曲折,但还是配置成功了,现分享如下. 原料 phpStudy 2018 VSCODE 配置过程 安装 phpStudy ...

  2. Java]运算符优先级

    https://blog.csdn.net/xiaoli_feng/article/details/4567184

  3. php设计模式之职责链模式

    <?php /** * @desc php设计模式之职责链模式(责任链模式) 定义:顾名思义,责任链模式为请求创建了一个接收者对象的链.这种模式给予请求的类型,对请求的发送者和接收者进行解耦.这 ...

  4. listener does not currently know of 。。。。

    打开Oracle的 listener.ora 文件: (../oracle/product/10.2.0/db_1/network/admin/listener.ora) 设置环境变量: Oracle ...

  5. 从c#数组求和说起

    c#是一种玩具语言 为什么这么说, 举个简单的例子,提问:对数组[1,2,3]求和有几种方法? 我能说出来的,四种.说出来,不是上网查出来. for,foreach,sum,while. for好像大 ...

  6. BASIC-17_蓝桥杯_矩阵乘法

    示例代码: #include <stdio.h>#define N 30 int main(void){ int n = 0 , m = 0 , sum = 0; int i = 0 , ...

  7. BASIC-12_蓝桥杯_十六进制转八进制

    总结: 1.使用库函数可有效节省空间,但时间花费较多; 2.由于本题的输入数据较大,又限制时间,故要注意利用空间换时间; 3.使用顺序结构换取最小运行时间; 示例代码: #include <st ...

  8. C/C++基础----string, vector, array

    1 using声明 方便使用命名空间中的成员,不用每次xxx::yyy 头文件不应该包含using声明(不经意间包含了一些名字) 2 string 表3.1:初始化string对象的方式 string ...

  9. session 、cookie、token的区别

    session session的中文翻译是“会话”,当用户打开某个web应用时,便与web服务器产生一次session.服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后sess ...

  10. bzoj 2571: Getting Rid of the Holidays

    Description B国的国王Johnny在他在位的短短几年里制定了不少的节日(事实上没超过30个),这些节日是为了尊敬各种各样他所想到的东西而设立的.每过一段固定的时间,一个节日将会被举行(即节 ...