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 SPI机制:ServiceLoader实现原理及应用剖析
一.背景 SPI,全称Service Provider Interfaces,服务提供接口.是Java提供的一套供第三方实现或扩展使用的技术体系.主要通过解耦服务具体实现以及服务使用,使得程序的可扩展 ...
- 【java编程】ServiceLoader使用看这一篇就够了
转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...
- JDBC【3】-- SPI技术使用以及在数据库连接中的使用
目录 1.SPI是什么? 2.如何使用SPI来提供自定义服务? 2.1 DBInterface定义接口 2.2 模拟Mysql实现驱动 2.3 模拟SqlServer实现驱动 2.4 模拟用户使用不同 ...
- JAVA SPI(Service Provider Interface)原理、设计及源码解析(其一)
背景 团队内部轮流技术分享,其他人都是分享源码,我每次都是设计和架构,感觉自己太特立独行.这次我要合群点,分享点源码. 概念 Service Provider Interface:服务提供方接口.是一 ...
- ServiceLoader详解
系统中用到了ServiceLoader,查了一下: ServiceLoader与ClassLoader是Java中2个即相互区别又相互联系的加载器.JVM利用ClassLoader将类载入内存,这是一 ...
- SpringBoot初体验及原理解析
一.前言 上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对“约定大于配置(Conv ...
- Dubbo原理和源码解析之“微内核+插件”机制
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- dubbo实现原理之SPI简介
dubbo采用微内核+插件体系,设计优雅,扩展性很强.微内核+插件体系是如何实现的呢?想必大家都知道SPI(service provider interface)机制.这种机制的原理是假如我们定义了服 ...
- Dubbo学习笔记6:Dubbo增强SPI与SPI中扩展点自动包装的实现原理
在Dubbo整体架构分析中介绍了Dubbo中除了Service和Config层为API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,也就是扩展性比较强,这也是Dubbo比较好的 ...
随机推荐
- velocity 知识点
velocity 教程: http://www.51gjie.com/javaweb/126 velocity 语法 语法 说明 关键字以#开头 定义数组 ['aaa','bbb'] 变量以$开头 把 ...
- druid:阿里巴巴开源,数据库连接池管理
官方地址:https://github.com/alibaba/druid
- python3 获取int最大值
python2 中获取int最大值 import sys i = sys.maxint print i 但是在python3中,报错: AttributeError: module 'sys' has ...
- http常用状态码说明
HTTP状态码:每发出一个http请求之后,就会有一个响应,http本身会有一个状态码,来标示这个请求是否成功,常见状态码: 200,2开头的都表示这个请求发送成功,最常见的就是200 300,3开头 ...
- VS2010编译和运行项目错误
打开工程提示如下: The 'Microsoft.Data.Entity.Design.BootstrapPackage.BootstrapPackage, Microsoft.Data.Entity ...
- NPOI 创建Excel 设置宽度 样式 颜色对比表
前两天用NPOI来操作Office软件,在使用的时候有点问题,也有收获,就做个笔记 记录下来,主要做的事数据的导出功能.一些公共的方法,做个笔记. 更多的详细内容可以到NPOI的官方教程去看 htt ...
- Robomongo,Mongo可视化工具
哇唔,其实她是三(阴险脸). 你看你看,界面清新,让人家心旷神怡(害羞),谁还想win+R+mongo呀呀呀?! 哎呀呀,继续···说正事. 在这里···借助SQL进一步理解下MongoDB SQL术 ...
- Eclipse引入jar包的三种方式
引用自 http://chenxu.wo.blog.163.com/blog/static/50239687201162310620634/ 使用Eclipse开发应用程序,少不了使用第三方jar包, ...
- C++ STL Set 集合
前言 set是STL中的一种关联容器.集合具有无序性,互异性等特点.熟练使用STL中的set模板类,可以比较简单的解决一些编程问题. 关联容器:元素按照关键字来保存和访问,STL中的map,set就是 ...
- Date类型之继承方法
ECMAScript中的Date类型是在早期Java中的java.util.Date类型基础上构建的.为此,Date类型使用自UTC(国际协调时间)1970年1月1日午夜零时开始经过的毫秒数来保存日期 ...