Spring SPI 机制总结
1、概念:
SPI(Service Provider Interface)服务提供接口,简单来说就是用来解耦,实现插件的自由插拔,具体实现方案可参考JDK里的ServiceLoader(加载classpath下所有META-INF/services/目录下的对应给定接口包路径的文件,然后通过反射实例化配置的所有实现类,以此将接口定义和逻辑实现分离)
Spring在3.0.x的时候就已经引入了spring.handlers,很多博客讲Spring SPI的时候并没有提到spring.handlers,但是通过我自己的分析对比,其实spring.handlers也是一种SPI的实现,只不过它是基于xml的,而且在没有boot的年代,它几乎是所有三方框架跟spring整合的必选机制。

在3.2.x又新引入了spring.factories,它的实现跟JDK的SPI就基本是相似的了。

spring.handlers和spring.factories我都把它归纳为Spring为我们提供的SPI机制,通过这两种机制,我们可以在不修改Spring源码的前提下,非常轻松的做到对Spring框架的扩展开发。
2、实现:
2.1 先看看spring.handlers SPI
在Spring里有个接口NamespaceHandlerResolver,只有一个默认的实现类DefaultNamespaceHandlerResolver,而它的作用就是加载classpath下可能分散在各个jar包中的META-INF/spring.handlers文件,resolve方法中关键代码如下:
//加载所有jar包中的META-INF/spring.handlers文件
Properties mappings=
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
//把META-INF/spring.handlers中配置的namespaceUri对应实现类实例化
NamespaceHandler namespaceHandler =
(NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
DefaultNamespaceHandlerResolver.resolve()主要被用在BeanDefinitionParserDelegate的parseCustomElement和decorateIfRequired,所以spring.handlers SPI机制主要也是被用在bean的扫描和解析过程中。
2.2 再来看spring.factories SPI
// 获取某个已定义接口的实现类,跟JDK的ServiceLoader SPI相似度为90%
List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(BeanInfoFactory.class, classLoader);
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有jar文件中找到MET-INF/spring.factories文件(注意是:classpath下的所有jar包,所以可插拔、扩展性超强)
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String propertyValue = properties.getProperty(factoryClassName);
for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
result.add(factoryName.trim());
}
}
return result;
更多细节,大家可以参考SpringFactoriesLoader类,Spring自3.2.x引入spring.factories SPI后其实一直没怎么利用起来,只有CachedIntrospectionResults(初始化bean的过程中)用到了,而且在几大核心jar包里,也只有bean包里才有用到。
真正把spring.factories发扬光大的,是到了Spring Boot,可以看到boot包里配置了非常多的接口实现类。大家跟踪boot的启动类SpringApplication可以发现,有很多地方都调用了getSpringFactoriesInstances()方法,这些就是spring boot开给我们的扩展机会,就像一座宝藏一样,大家可以自己去发掘。

3、应用:
先来看看mybatis和dubbo早期跟Spring整合的实现,他们无一例外都用到了spring.handlers SPI机制,以此来向IOC容器注入自己的Bean。


进入boot时代后,spring.factories SPI机制应用得更加广泛,我们可以在容器启动、环境准备、初始化、上下文加载等等环节轻轻松松的对Spring做扩展开发(例如:我们项目中用到spring.factories SPI机制对配置文件中的变量实现动态解密,以及前篇博文中提到的@Replace注解等)。
4、实践(加载application.xyz配置文件):
Spring里有两种常见的配置文件类型:application.properties 和 application.yml,其中yml是近年兴起的,但说实话同事也包括我自己是被它坑过,没有合适的编辑器时很容易把格式写错,导致上线出问题。所以我就在想有没有办法让Spring支持一种新的配置文件格式,既保留yml的简洁优雅,有能够有强制的格式校验,暂时我想到了json格式。

# 这是spring.factories中的配置
org.springframework.boot.env.PropertySourceLoader=top.hiccup.json.MyJsonPropertySourceLoader
public class MyJsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[]{"xyz"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
// 这里只是做了简单解析,没有做嵌套配置的解析
JSONObject json = JSONObject.parseObject(sb.toString());
List<PropertySource<?>> propertySources = new ArrayList<>();
MapPropertySource mapPropertySource = new MapPropertySource(resource.getFilename(), json);
propertySources.add(mapPropertySource);
return propertySources;
}
}
ConfigurableApplicationContext ctx = SpringApplication.run(BootTest.class, args);
Custom custom = ctx.getBean(Custom.class);
System.out.println(custom.name);
System.out.println(custom.age);
具体代码可以参考(https://github.com/hiccup234/web-advanced/tree/master/configFile) ,运行得到结果如下:

可见我们在不修改Spring源码的前提下,轻松通过Spring开放给我们的扩展性实现了对新的配置文件类型的加载和解析。
这就是Spring SPI的魅力吧。
Spring SPI 机制总结的更多相关文章
- 聊聊Java SPI机制
一.Java SPI机制 SPI(Service Provider Interface)是JDK内置的服务发现机制,用在不同模块间通过接口调用服务,避免对具体服务服务接口具体实现类的耦合.比如JDBC ...
- 一文搞懂Java/Spring/Dubbo框架中的SPI机制
几天前和一位前辈聊起了Spring技术,大佬突然说了SPI,作为一个熟练使用Spring的民工,心中一紧,咱也不敢说不懂,而是在聊完之后赶紧打开了浏览器,开始的学习之路,所以也就有了这篇文章.废话不多 ...
- java 的SPI机制
今天看到spring mvc 使用Java Validation Api(JSR-303)进行校验,需要加载一个 其具体实现(比如Hibernate Validator), 本来没有什么问题,但是突然 ...
- Dubbo 源码分析 - SPI 机制
1.简介 SPI 全称为 Service Provider Interface,是 Java 提供的一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加 ...
- dubbo源码分析4——SPI机制_ExtensionFactory类的作用
ExtensionFactory的源码: @SPI public interface ExtensionFactory { /** * Get extension. * * @param type o ...
- dubbo源码分析1——SPI机制的概要介绍
插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单, ...
- JDK源码解析之Java SPI机制
1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...
- dubbo的spi机制
SPI SPI是一种扩展机制,在java中SPI机制被广泛应用,比如Spring中的SpringServletContainerInitializer 使得容器启动的时候SpringServletCo ...
- 【Java】深入理解Java中的spi机制
深入理解Java中的spi机制 SPI全名为Service Provider Interface是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用 ...
随机推荐
- 漫漫Java路1—基础知识2—注释和命名规则
## 注释 1. 单行注释 ```java //这是一个注释 ``` 2. 多行注释 ```java /* 这是一个注释 */ ``` 3. 文档注释 ```java /** * * * */ ``` ...
- 微服务网关Zuul过滤器Filter
Zuul本质 Zuul是一个网关,关于网关的介绍参考:亿级流量架构之网关设计思路.常见网关对比, 可知Zuul是一个业务网关, 而深入了解Zuul, 基本就是一系列过滤器的集合: Zuul的过滤器 下 ...
- [笔记] 扩展Lucas定理
[笔记] 扩展\(Lucas\)定理 \(Lucas\)定理:\(\binom{n}{m} \equiv \binom{n/P}{m/P} \binom{n \% P}{m \% P}\pmod{P} ...
- java IO NIO BIO 最权威的总结
1. BIO (Blocking I/O) 1.1 传统 BIO 1.2 伪异步 IO 1.3 代码示例 1.4 总结 2. NIO (New I/O) 2.1 NIO 简介 2.2 NIO的特性/N ...
- 攻防世界 reverse elrond32
tinyctf-2014 elrond32 1 int __cdecl main(int a1, char **arg_input) 2 { 3 if ( a1 > 1 && c ...
- JAVA题目:正整数n若是其平方数的尾部,则称n为同构数 如:5*5=25, 25*25=625 问: 求1~99中的所有同构数
1 /*题目:正整数n若是其平方数的尾部,则称n为同构数 2 如:5*5=25, 25*25=625 3 问: 求1~99中的所有同构数 4 */ 5 //分析:将1-99分为1-9和10-99,用取 ...
- java例题_35 找到最大值和最小值并交换位置
1 /*35 [程序 35 最大最小交换] 2 题目:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组. 3 */ 4 5 /*分析 6 * 1.先初始化一个数组,然后从键盘获得值 ...
- Dynamics CRM9.0更新了Chrome后菜单按钮变形
前段时间Chorme更新后Dynamics CRM9.0的系统菜单样式变的很难看 具体修改方法如下: 找到Dynamics CRM安装目录C:\Program Files\Microsoft Dyna ...
- 安装Dynamics CRM Report出错二
提示账户不是本地用户且不受支持 找到所需的服务,使用域管理员用户更改服务运行的账户.应用和确定 重新启动服务 重新运行安装向导,环境验证成功
- 【CTF】CTFHub 技能树 彩蛋 writeup
碎碎念 CTFHub:https://www.ctfhub.com/ 笔者入门CTF时时刚开始刷的是bugku的旧平台,后来才有了CTFHub. 感觉不论是网页UI设计,还是题目质量,赛事跟踪,工具软 ...