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. ...
随机推荐
- (二)JavaScript 输出
avaScript 没有任何打印或者输出的函数. JavaScript 显示数据 JavaScript 可以通过不同的方式来输出数据: 使用 window.alert() 弹出警告框. 使用 docu ...
- linux内存源码分析 - 内存回收(匿名页反向映射)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 看完了内存压缩,最近在看内存回收这块的代码,发现内容有些多,需要分几块去详细说明,首先先说说匿名页的反向映 ...
- UVA - 10931-Parity
题意:1.输入一个数,将其转换为二进制.2.记录二进制中出现1的次数. 注意:转换二进制后直接输出,不能转换为十进制后输出 #include<iostream> #include<c ...
- C#发邮件_EmailHelper
EmailHelper类 public class EmailHelper { /// <summary> /// 发送邮件 /// </summary> /// <pa ...
- JSP报错:The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path
今天使用Eclipse+Maven建立了一个Javaweb工程,并在eclipse中配置了Web容器Jboss eap 6.2.新建jsp页面,添加一个简单 的Java类.可是,JSP页面顶端出现“红 ...
- [原创]Sharding-Sphere之Proxy初探
大家好,拓海(https://github.com/tuohai666)今天为大家分享Sharding-Sphere推出的重磅产品:Sharding-Proxy!在之前闪亮登场的Sharding-Sp ...
- Randomized Online PCA Algorithms with Regret Bounds that are Logarithmic in the Dimension
目录 Setup of Batch PCA and Online PCA Hedge Algorithm 改进算法 用于矩阵 \(rounding()\) 前俩次,都用到了\(rounding()\) ...
- Mysql数据库触发器调用脚本
一.数据库触发器 mysql触发器trigger 实例详解 对数据库触发器new和old的理解 示例 二.UDF mySql的UDF是什么 三.安装执行命令UDF mysql触发器调用外部脚本(安装) ...
- JDK8-新特性-附demo
import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; impor ...
- .net之httphandler小记
本地调试代码遇到的一个问题,没有走URL路由器(UrlReWriter : IHttpHandlerFactory),于是网上科普了一下原理,主要有两点: 1.asp.net在处理http请求时,会由 ...