Spring SPI 机制总结

摘要:
在3.2.x又新引入了spring.factories,它的实现跟JDK的SPI就基本是相似的了。spring.handlers和spring.factories我都把它归纳为Spring为我们提供的SPI机制,通过这两种机制,我们可以在不修改Spring源码的前提下,非常轻松的做到对Spring框架的扩展开发。进入boot时代后,spring.factoriesSPI机制应用得更加广泛,我们可以在容器启动、环境准备、初始化、上下文加载等等环节轻轻松松的对Spring做扩展开发。

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整合的必选机制。
Spring SPI 机制总结第1张
在3.2.x又新引入了spring.factories,它的实现跟JDK的SPI就基本是相似的了。
Spring SPI 机制总结第2张
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开给我们的扩展机会,就像一座宝藏一样,大家可以自己去发掘。
Spring SPI 机制总结第3张

3、应用:

先来看看mybatis和dubbo早期跟Spring整合的实现,他们无一例外都用到了spring.handlers SPI机制,以此来向IOC容器注入自己的Bean。
Spring SPI 机制总结第4张
Spring SPI 机制总结第5张
进入boot时代后,spring.factories SPI机制应用得更加广泛,我们可以在容器启动、环境准备、初始化、上下文加载等等环节轻轻松松的对Spring做扩展开发(例如:我们项目中用到spring.factories SPI机制对配置文件中的变量实现动态解密,以及前篇博文中提到的@Replace注解等)。

4、实践(加载application.xyz配置文件):

Spring里有两种常见的配置文件类型:application.properties 和 application.yml,其中yml是近年兴起的,但说实话同事也包括我自己是被它坑过,没有合适的编辑器时很容易把格式写错,导致上线出问题。所以我就在想有没有办法让Spring支持一种新的配置文件格式,既保留yml的简洁优雅,有能够有强制的格式校验,暂时我想到了json格式。
Spring SPI 机制总结第6张

# 这是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 SPI 机制总结第7张
可见我们在不修改Spring源码的前提下,轻松通过Spring开放给我们的扩展性实现了对新的配置文件类型的加载和解析。

这就是Spring SPI的魅力吧。

免责声明:文章转载自《Spring SPI 机制总结》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇jQuery获取Select选择的Text和Value(详细汇总)开放平台鉴权以及OAuth2.0介绍下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Spring MVC-学习笔记(4)数据绑定流程

1、请求数据在到达处理方法前这段时间。 spring MVC还会做请求信息转换、数据转换、数据格式化、数据校验。 2、数据转换 1》ConversionService: Spring类型转换体系的核心接口,在其中定义了一下4个方法: 可以在spring上下文中注册一个ConversionServiceFactoryBean,用以定义一个Conversio...

SpringBoot集成多数据源

一、多数据源集成简介 多数据源集成使用dynamic-sring-boot-starter,github地址:https://github.com/baomidou/dynamic-datasource-spring-boot-starter,详细使用可以参考github地址。 二、BDP框架集成多数据源步骤 1、pom.xm文件引入dynamic-dat...

dubbo协议参考手册(转)

原文链接:http://wely.iteye.com/blog/2331332 协议参考手册 (+) (#) 推荐使用Dubbo协议 性能测试报告各协议的性能情况,请参见:性能测试报告(+) dubbo:// (+) (#) Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服...

3.开始使用Spring Cloud实战微服务

                 开始使用Spring Cloud实战微服务 3.1. Spring Cloud实战前提               3.1.1. 需要的技术储备                   语言方面:可以使用Java、scala、Groovy...等等,推荐使用Java                   构建工具方面:Java...

springcloud(一)-初识

springCloud简介   尽管springCloud带有“cloud”字样,但它并不是云计算解决方案,而是在SpringBoot基础上构建的,用于快速构建分布式系统的通用的工具集。从技术架构上降低了对大型系统构建的要求,使我们以非常低的成本(技术或者硬件)搭建一套高效、分布式、容错的平台,但Spring Cloud也不是没有缺点,小型独立的项目不适合...

SpringBoot(3):SpringData 数据访问

一. 简介 Spring Data是一个用于简化数据库访问,并支持云服务的开源框架;其主要目标是 使得对数据的访问变得方便快捷。对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库)。Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库 Sping Data 官网:https://spri...