在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. dell support

    部門營業時間 電話號碼訂單支援中小型企業 (員工不多於 500名 )00852-3416-0910 9:00 - 18:00 訂單編號:  810607806 訂單日期:  26/11/2014 客戶 ...

  2. greasemonkey修改网页内指定函数

    greasemonkey replace function? 方法1:编写GM代码 alert("hello2"); var mydiv =document.getElementB ...

  3. 未能正确加载“VSTS for Database Professionals Sql Server Data-tier Application”包。(转)

    今天费了九牛二虎之力,重转好了vs2010之后,打开解决方案,报出下面的错误: ---------------------------Microsoft Visual Studio---------- ...

  4. winform 程序调用及参数调用

    调用程序: // 新轮廓 -> 调用轮廓扫描程序        private void toolStripMenuItem9_Click(object sender, EventArgs e) ...

  5. Gitlab Jenkins WebHook 持续集成配置踩坑记

    Jenkins相关介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 目的 配置Gitla ...

  6. JQuery返回Json日期格式的問題

    用JQuery Ajax返回一個Entity的Json數據時,如果Entity的屬性中有日期格式,那返回來的是一串字符串,如下圖所示: 在網上找了很久也沒有找到一個好的解決方案,最後自己寫一個java ...

  7. DokuWiki

    DokuWiki 1.点我,点我,dokuwiki使用教程一 2.点我,点我,dokuwiki使用教程二 3.点我,点我,dokuWiki语法 4.点我,点我,WARP插件

  8. sql表设计

    数据库实际上是系统逻辑在磁盘上的固化,是信息河流的蓄水池. 数据库的表应有如下类型 1)类表.配置表.作为业务逻辑基本的名字,状态的定义,作为构建逻辑世界的最基础框架,解释框架的框架. 特点,数据不会 ...

  9. mysql联表查询

    2.1 内连接 select a.*,b.* FROM  a INNER join  b  ON a.id = b.id;  查出所有 或者 select a.*,b.* FROM a join b ...

  10. [UE4]蓝图函数库小结

    蓝图函数库的功能非常强劲,如果在项目中使用的话有时能达到事半功倍的效果. 蓝图函数库,Blueprint Function Library.可以非常方便的将代码中的函数暴露给所有的蓝图使用,同时也提供 ...