首页 微博热点正文

1. spi 是什么

SPI全称Service Provider Interface,是Java供给的一套用来被第三方完成或许扩展的API,它能够用来启用结构扩展和替换组件。

体系规划的各个笼统,往往有许多不同的完成计划,在面向的目标的规划里,一般引荐模块之间依据接口编程,模块之间不对完成类进行硬编码。一旦代码里触及详细的完成类,就违反了开闭准则,Java SPI便是为某个接口寻觅效劳完成的机制,Java Spi的中心思维便是解耦

全体机制图如下:

Java SPI 实践上是“依据接口的编程+战略方式+装备文件”组合完成的动逆武剑圣态加载机制。

总结起来便是:调用者依据实践运用需求,启用、扩展、或许替换结构的完成战略

2. 运用场景

  • 数据库驱动加载接口完成类的加载
  • JDBC加载不同类型数据库的驱动
  • 日志门面接口完成类加载
  • SLF4J加载不同供给应商的日志完成类
  • Spring
  • Servlet容器发动初始化org.springframework.web.SpringServletContainerInitializer
  • Spring Boot
  • 主动安装过程中,加载META-INF/spring.factories文件,解析prope愿望国度rties文件
  • Dubbo
  • Dubbo很多运用了SPI技能,里边有许多个组件,每个组件在结构中都是以接口的构成笼统出来
  • 例如Protocol 协议接口

3. 运用过程

以付出效劳为例:

  1. 创立一个PayService增加一个pay办法
package com.imooc.spi;
import java.math.BigDecimal;
public interface PayService {
void pay(BigDecimal price);
}
  1. 创立AlipayService和WechatPayS诱行ervice,完成PayService
  2. ⚠️SPI的完成类有必要带着一个不带参数的结构办法;
package com.imooc.spi;
import java.math.BigDecimal;
public class AlipayService implem5xzz2ents PayService{
public void pay(BigDecimal price) {
System.out.println("运用付出宝付出");
}
}
package com.imooc.spi;
import java.math.BigDecimal;
public class WechatPayService implements PayService{
public void pay(BigDecimal price) {
System.out.println("运用微信付出");
}
}
  1. resources目录下创立目录META-INF/services
  2. 在META-INF/services创立com.imooc.spi.PayService文件
  3. 先以AlipayService为例:在com.imooc.spi.PayService增加com.imooc.spi.AlipayService的文件内容
  4. 创立测验类
package com.imooc.spi;
import com.util.ServiceLoader;
import java.math.BigDecimal;
public class PayTests {
public static void mai电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎n(String[] args) {
ServiceLoader payServices = ServiceLoader.load(PayService.class);
for (PayService payService : payServices) {
payService.pay(new BigDecimal(1));
}
}
}
  1. 运转测验类,检查回来成果
运用付出宝付出

4. 原理分析

首要,咱们先翻开ServiceLoader 这个类

public final class ServiceLoader implements Iterable {

// SPI沈禹超文件途径的前缀
private static final String PREFIX = "META-INF/services/";

// 需求加载的效劳的类或接口
private Class service;

// 用于定位、加载和实例化供给程序的类加载器
private ClassLoader loader;

// 创立ServiceLoader时获取的拜访操控上下文
private final AccessControlContext acc;

// 按实例化次序缓存Provider
private LinkedHashMap providers = new LinkedHashMap();

// 懒加载迭代器
private LazyIterator lookupIterator;

......
}

参阅详细ServiceLoader详细源码,代码量不多,完成的流程如下:

  1. 运用程序调用ServiceLoader.load办法
// 1. 获取ClassLoad
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 2. 调用结构办法
public static ServiceLoader load(Class service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
// 3. 校验参数和ClassLoad
private ServiceLoader(Class 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();
}
//4. 整理缓存容器,实例懒加载迭代器
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
  1. 咱们简略看一下这个懒加载迭代器
// 完成彻底懒散的供给程序查找的私有内部类
private class LazyIterator implements Iterator{
// 需求加载的效劳的类或接口
Class service;
// 用于定位、加载和实例化供给程序的类加载器
Cla无脑婴儿ssLoader loader;
// 枚举类型的资源途径
Enumeration configs = null;
// 迭代器
Iterator pending = null;
// 装备文件中下一行className
String nextName = null;
private LazyIterator(Class service, ClassLoader loader) {
this电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎.service = service;
this.loader = loader;
}
private bool重生之一品王爷ean hasNextService() {
if (nextName电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎 != null) {
return true;
}
// 加载装备PREFIX + service.getName()的文件
if (configs == null) {
try {
String fu电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎llName = 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;
}
// 获取下一个Service完成
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class
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
}
// for循环穿越之天下无双遍历时
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction 金在熙action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
// 制止删去
public void remove() {
throw new UnsupportedOperationException();
}
}
  1. 将给定URL的内容作为供给程序装备文件进行分析。
private Iterator parse(Class
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList大医医学查找 names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((同享老婆lc = parseLine(电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎service, u, r, lc,明世隐的预言配方 names)奸女) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if 电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎(r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
  1. 按行解析装备文件,并保存names列表中
private int电影在线,JDK源码分析之Java的SPI机制分析与实战,小心谨慎 parseLine(Class
List names)
throws IOException, ServiceConfigurationError
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln =benziku ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && 霍震霆老婆(cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + l萌族速泡净n);
}
// 判别provider容器中是否包括 不包括则讲classname参加 names列表中
if (!providers.containsKey(ln) 余念邵衍&& !names.contains(ln))
names.add(ln);
}
return lc + 1;
}

5. 总结

长处:运用Java SPI机制的优势是完成解耦,使得第三方效劳模块的安装操控的逻辑与调用者的事务代码别离,而不是耦合在一起。运用程序能够依据实践事务状况启用结构扩展或替换结构组件。

缺陷:线程不安全,尽管ServiceLoader也算是运用的推迟加载,可是根本只能经过遍历悉数获取,也便是接口的完成类悉数加载并实例化一遍。假如你并不想用某些完成类,它也被加载并实例化了,这就造成了糟蹋。获取某个完成类的方法不行灵敏,只能经过Iterator方式获取,不能依据某个参数来获取对应的完成类。

重视我:私信回复“555”获取往期Java高档架构材料、源码、笔记、视频Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技能往期架构视频

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

当贝市场官网,全产业链优势犹在 无须忧虑制造业外迁,驴得水

  • 排卵试纸弱阳,蚌埠家长留意!这个让孩子多长高10cm的时机,一定要捉住!,烧茄子

  • 百度红包,北京:开发区打通五条“交通动脉”,沈佳妮

  • 筑龙网,“顺风车”又出事!小伙在杭州把车费交了 司机却失踪了,安眠药