ServiceLoader详解
系统中用到了ServiceLoader,查了一下:
ServiceLoader与ClassLoader是Java中2个即相互区别又相互联系的加载器.JVM利用ClassLoader将类载入内存,这是一个类声明周期的第一步(一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况)。详情请参阅:详解Java类的生命周期
那ServiceLoader又是什么呢?ServiceLoader:一个简单的服务提供者加载设施。服务 是一个熟知的接口和类(通常为抽象类)集合。服务提供者 是服务的特定实现。提供者中的类通常实现接口,并子类化在服务本身中定义的子类。服务提供者可以以扩展的形式安装在 Java 平台的实现中,也就是将 jar 文件放入任意常用的扩展目录中。也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用。……唯一强制要求的是,提供者类必须具有不带参数的构造方法,以便它们可以在加载中被实例化。
通过在资源目录META-INF/services中放置提供者配置文件 来标识服务提供者。文件名称是服务类型的完全限定二进制名称。该文件包含一个具体提供者类的完全限定二进制名称列表,每行一个。忽略各名称周围的空格、制表符和空行。注释字符为'#'('\u0023', NUMBER SIGN);忽略每行第一个注释字符后面的所有字符。文件必须使用 UTF-8 编码。
以延迟方式查找和实例化提供者,也就是说根据需要进行。服务加载器维护到目前为止已经加载的提供者缓存。每次调用 iterator 方法返回一个迭代器,它首先按照实例化顺序生成缓存的所有元素,然后以延迟方式查找和实例化所有剩余的提供者,依次将每个提供者添加到缓存。可以通过 reload 方法清除缓存。
……
以上来源于Java API里的说明,也许说的很专业,让我们有点晕头转向,我们可以简单的认为:ServiceLoader也像ClassLoader一样,能装载类文件,但是使用时有区别,具体区别如下:(1) ServiceLoader装载的是一系列有某种共同特征的实现类,而ClassLoader是个万能加载器;(2)ServiceLoader装载时需要特殊的配置,使用时也与ClassLoader有所区别;(3)ServiceLoader还实现了Iterator接口。[如有错误或不到的地方敬请指出,互相学习:)]
下面是关于ServiceLoader的简单的例子,仅供参考
(1)基础服务:IService
|
1
2
3
4
5
|
package com.service;public interface IService { String sayHello(); String getScheme();} |
(2)具体服务实现1:HDFSService
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.impl;import com.service.IService;public class HDFSService implements IService { @Override public String sayHello() { return "Hello HDFSService"; } @Override public String getScheme() { return "hdfs"; }} |
(3)具体服务实现2:LocalService
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.impl;import com.service.IService;public class LocalService implements IService { @Override public String sayHello() { return "Hello LocalService"; } @Override public String getScheme() { return "local"; }} |
(4)配置:META-INF/services/com.service.IService
|
1
2
|
com.impl.HDFSServicecom.impl.LocalService |
(5)测试类
|
1
2
3
4
5
6
7
8
9
10
11
|
package com.test;import java.util.ServiceLoader;import com.service.IService;public class Test { public static void main(String[] args) { ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class); for(IService service : serviceLoader) { System.out.println(service.getScheme()+"="+service.sayHello()); } }} |
结果:
hdfs=Hello HDFSService
local=Hello LocalService
可以看到ServiceLoader可以根据IService把定义的两个实现类找出来,返回一个ServiceLoader的实现,而ServiceLoader实现了Iterable接口,所以可以通过ServiceLoader来遍历所有在配置文件中定义的类的实例。
ServiceLoader的应用
(1)Hadoop FileSystem
Hadoop FileSystem就是通过这个机制来根据不同文件的scheme来返回不同的FileSystem。
|
1
2
3
4
5
6
7
8
9
10
11
|
private static void loadFileSystems() { synchronized(FileSystem.class){ if(!FILE_SYSTEMS_LOADED) { ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem.class); for(FileSystem fs : serviceLoader) { SERVICE_FILE_SYSTEMS.put(fs.getScheme(),fs.getClass()); } FILE_SYSTEMS_LOADED= true; } } } |
对应的配置文件:
|
1
2
3
4
5
6
7
|
org.apache.hadoop.fs.LocalFileSystem org.apache.hadoop.fs.viewfs.ViewFileSystem org.apache.hadoop.fs.s3.S3FileSystem org.apache.hadoop.fs.s3native.NativeS3FileSystem org.apache.hadoop.fs.kfs.KosmosFileSystem org.apache.hadoop.fs.ftp.FTPFileSystem org.apache.hadoop.fs.HarFileSystem |
通过之前的测试类输出对应的scheme和class如下:
file=class org.apache.hadoop.fs.LocalFileSystem
viewfs=class org.apache.hadoop.fs.viewfs.ViewFileSystem
s3=class org.apache.hadoop.fs.s3.S3FileSystem
s3n=class org.apache.hadoop.fs.s3native.NativeS3FileSystem
kfs=class org.apache.hadoop.fs.kfs.KosmosFileSystem
ftp=class org.apache.hadoop.fs.ftp.FTPFileSystem
har=class org.apache.hadoop.fs.HarFileSystem
hdfs=class org.apache.hadoop.hdfs.DistributedFileSystem
hftp=class org.apache.hadoop.hdfs.HftpFileSystem
hsftp=class org.apache.hadoop.hdfs.HsftpFileSystem
webhdfs=class org.apache.hadoop.hdfs.web.WebHdfsFileSystem
可以看到FileSystem会把所有的FileSystem的实现都以scheme和class来cache,之后就从这个cache中取相应的值。因此,以后可以通过ServiceLoader来实现一些类似的功能,而不用依赖像Spring这样的第三方框架。
(2)责任链模式
责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
责任连模式可以使用ServiceLoader实现具体服务对象的迭代加载并处理,为了确保此模式的灵活性,建议判断逻辑通过配置文件或数据库的方式,具体实现方式见 参考链接(2) 消灭成堆的……
一、使用场景
一般使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:
HelloService service = new HelloImpl();
如果需要动态的获取一个接口的实现类呢?全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,一般不会这么做。一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK给我们提供的TestServiceLoader 就是这种方式。
二、使用方式
在实现类的jar包的META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。
通过以下的例子来分析实现原理.
1. 新建一个接口,2个实现类。
package com.test.loader;
public interface HelloService {
public void sayHello();
}
package com.test.loader;
public class Dog implements HelloService {
@Override
public void sayHello() {
System.out.println("bark bark bark...");
}
}
package com.test.loader;
public class Sheep implements HelloService {
@Override
public void sayHello() {
System.out.println("bleat bleat bleat...");
}
}
2. 分别把接口、2个实现类打成3个jar包,放在D盘下。
3. 在Dog.jar、Sheep.jar分别加上META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。如下:
4. 使用指定的ClassLoader不包含实现类
public static void notInTheClassLoader() throws MalformedURLException {
ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar") },
TestServiceLoader.class.getClassLoader().getParent());
/* 指定的ClassLoader没有实现类,所以扫描不到META-INF/services/com.test.loader.HelloService */
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
结果不会打印任何信息。
5. 指定的ClassLoader包含实现类
public static void inTheClassLoader() throws MalformedURLException {
ClassLoader serviceCL = new URLClassLoader(
new URL[] { new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
TestServiceLoader.class.getClassLoader().getParent());
/* 实现类在指定的ClassLoader,所以可以扫描META-INF/services/com.test.loader.HelloService */
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
结果如下:
bark bark bark...
bleat bleat bleat...
6. 使用指定的ClassLoader加载接口类,不指定ClassLoader加载实现类。
public static void defaultClassLoader() throws MalformedURLException, ClassNotFoundException {
ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar"),
new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
TestServiceLoader.class.getClassLoader().getParent());
/* 默认会使用 ClassLoader.getSystemClassLoader() */
ServiceLoader<HelloService> helloServices = ServiceLoader
.load(((Class<HelloService>) (serviceCL.loadClass("com.test.loader.HelloService"))));
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
结果不打印任何信息。
7. 把2个实现jar加到工程的Build Path里面,不指定ClassLoader。
public static void notSpecifyClassLoader() {
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
结果如下:
bark bark bark...
bleat bleat bleat...
三、简单解析ServiceLoader
1. 构造函数,如果不指定ClassLoader或者指定的为null,则使用ClassLoader.getSystemClassLoader() ,即AppClassLoader。
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
2. 遍历有两个方法
hasNext会调用hasNextService,如果指定的ClassLoader为空(一般不会为空,构造函数会初始化),则调用ClassLoader.getSystemClassLoader().getResources,否则调用指定的ClassLoader的getResources方法,获取META-INF/services/接口的全限定名称,如META-INF/services/com.test.loader.HelloService,从这个文件中找出实现类全限定名称。
next会调用nextService,根据hasNextService获取的实现类信息,使用指定的ClassLoader进行加载和实例化。
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
注:以上代码基于JDK1.8.0_144。
ServiceLoader详解的更多相关文章
- Dubbo微容器(Cooma)详解
ExtensionLoader ExtensionLoader是Dubbo中的SPI的实现方法,它是Dubbo框架的微容器,也为框架提供各种组件的扩展点 三种注解 SPI Adaptive Activ ...
- JDBC详解系列(二)之加载驱动
---[来自我的CSDN博客](http://blog.csdn.net/weixin_37139197/article/details/78838091)--- 在JDBC详解系列(一)之流程中 ...
- hadoop2.7之作业提交详解(上)
根据wordcount进行分析: import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; impo ...
- Java SPI详解
1.什么是SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件. SPI的作用就是为这些被扩展的 ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
- Java SPI机制详解
Java SPI机制详解 1.什么是SPI? SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.SPI是一种动态替换发现的机制, 比如有个 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
随机推荐
- 定义静态map
public final static Map<String, String> header = new HashMap<String, String>(); static { ...
- python中如何对待易过期的cookies
有时候,我们进行爬虫操作是,会使用reques的的post函数携带cookies访问目标网站已达到登录或者其他 目的,笔者最近就遇到了这样的案例,周六写好的代码,周一过来就不行了,重新登录访问目标网页 ...
- 初学Python——软件目录结构规范
为什么要设计好目录结构? 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等.从而非常快速的了解这个项目. 可维护性高: 定义好组织 ...
- 使用webstrom开发react-native时react-native代码会出现红色下划线的解决方法
问题:使用webstrom开发react-native时react-native代码会出现红色下划线的解决方法 解决方法:webstrom ->preferences->Laugrange ...
- face recognition[MobileFaceNet]
本文来自<MobileFaceNets: Efficient CNNs for Accurate Real-Time Face Verification on Mobile Devices> ...
- nova系列二:kvm介绍
一 什么是kvm KVM 全称 Kernel-Based Virtual Machine.也就是说 KVM 是基于 Linux 内核实现的,这就使得linux内核本身就相当于一个Hypervisor. ...
- java单例模式总结
目录 一. 饿汉模式(静态初始化) 二.双重检查锁(dcl) 三. 延迟占位类 四.枚举实现 最后 常见安全的单例实现代码和自己的一点理解. 一. 饿汉模式(静态初始化) class Singleto ...
- 【原创】从策略模式闲扯到lambda表达式
引言 策略模式,讲这个模式的文章很多,但都缺乏一个循序渐进的过程.讲lambda表达式的文章也很多,但基本都是堆砌一堆的概念,很少带有自己的见解.博主一时兴起,想写一篇这二者的文章.需要说明的是,在看 ...
- 在GridControl控件上绑定图片的几种操作方式
我们知道,基于DevExpress的开发Winform的项目界面的时候,GridControl控件是经常用来绑定数据的,一般以常规的字符内容为主,有时候也会有图片的显示需要,那么如果显示图片,我们应该 ...
- 容器技术|Docker三剑客之docker-compose
三剑客简介 docker-machine docker技术是基于Linux内核的cgroup技术实现的,那么问题来了,在非Linux平台上是否就不能使用docker技术了呢?答案是可以的,不过显然需要 ...