前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)

【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)

作者头像
YourBatman
发布2019-09-03 16:23:14
1.8K0
发布2019-09-03 16:23:14
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

前言

在之前的一篇文章【小家Spring】Spring注解驱动开发—Servlet 3.0整合Spring MVC(不使用web.xml部署描述符,使用ServletContainerInitializer)

它介绍了基于注解驱动的Servlet容器的启动。今天刚好回头看到了自己写的这篇文章,自己心里就萌生了几个疑问:原理是啥?为何就能自动的这么样执行呢?通过配置文件就能加载类这肯定涉及到类加载机制吧?

带着这些疑问,就决定深究一番,然后做出如下记录,供读者们参考哈~~~

ServiceLoader:服务提供者加载器

SPI概念介绍

SPI:Service Provider Interfaces(服务提供者接口)。正如从SPI的名字去理解SPI就是Service提供者接口

SPI定位:给服务提供厂商扩展框架功能的开发者使用的接口。

比如大名鼎鼎的JDBC驱动,Java只提供了java.sql.Driver这个SPI接口,具体的实现由各服务提供厂商(比如MySql、Oracle等)去提供。Mysql的驱动实现类为:com.mysql.jdbc.Driver,Oracle的驱动实现类为:oracle.jdbc.driver.OracleDriver,PostgreSQL 的为:org.postgresql.Driver

ServiceLoader

首先我们简单的看看javadoc和源码字段说明

代码语言:javascript
复制
/**
* 一个简单的服务提供商加载设施
* 服务 是一个熟知的接口和类(通常为抽象类)集合。服务提供者 是服务的特定实现
* 服务提供者可以以扩展的形式安装在 **Java 平台的实现中**.也就是将 jar 文件放入任意常用的扩展目录中
* 也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用(所以并不限定你的方式,不在类路径也无所谓哟)
*/
// @since 1.6 Java6以后才有的工具类
public final class ServiceLoader<S> implements Iterable<S> {
	// 这个路径非常的重要,最终就是去此路径读取
    private static final String PREFIX = "META-INF/services/";
    // 指向对象类型的 Class<S> 对象  最后通过 Class<S> 对象来构造服务实现类 S 的实例 s 
    private final Class<S> service;
	
	//类加载器 ClassLoader
    private final ClassLoader loader;
    private final AccessControlContext acc;
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    private LazyIterator lookupIterator;
	
	...
}
ServiceLoader与ClassLoader

ServiceLoaderClassLoader是Java中2个即相互区别又相互联系的加载器。(ServiceLoader是一种加载类的规范,底层还是依赖于ClassLoader的)

JVM利用ClassLoader将类载入内存,这是一个类生命周期的第一步。(一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况。 Tips:初始化:给类中的静态变量赋予正确的初始值)

ServiceLoader: 上面JavaDoc里已经有了一些概念性的说明。可以说它的使用是非常松散的,没有太多的强制要求。 唯一强制要求的是提供者类(实现类)必须具有不带参数的构造方法,以便它们可以在加载中被实例化(因此,子接口肯定不行(下面Demo会验证))

ServiceLoader位于java.util包,ClassLoader位于java.lang包。因此可议看出它俩的定位也是不一样的,ServiceLoader被认为是一种工具

我们可以简单的认为:ServiceLoader也像ClassLoader一样,能装载类文件,但是使用时有区别,具体区别如下:

  1. ServiceLoader装载的是一系列有某种共同特征的实现类,而ClassLoader是个万能加载器;
  2. ServiceLoader装载时需要特殊的配置,使用时也与ClassLoader有所区别;
  3. ServiceLoader还实现了Iterator接口。
ServiceLoader使用方式

毕竟光说不练假把式

我从这个问题的抛出,然后逐步讲解:

一般我们使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:

代码语言:javascript
复制
HelloService service = new HelloImpl();

如果需要动态的获取一个接口的实现类呢?

全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,相信没人愿意去这么做吧。

一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。而JDK为我们提供的工具ServiceLoader就是采用的这种方式

(该思想其实在Spring体系内,存在大量的使用,并且我觉得比JDK做得还好~~),当然JDK的好处是:它是规范,更容易广而周知,通用性更强

ServiceLoader它的使用方式可列为4个步骤:

  1. 创建一个接口文件
  2. 在resources资源目录下创建META-INF/services文件夹
  3. 在上面services文件夹中创建文件:以接口全类名命名
  4. 在该文件内,写好实现类的全类名们

使用Demo如下:

代码语言:javascript
复制
// SPI服务接口
public interface IService {
    String sayHello();

    String getScheme();
}

// 主要为了测试,看看是子接口是否会被加载
public interface MyIService extends IService {

}

准备实现类(两个),模拟服务提供商:

代码语言:javascript
复制
// 服务提供商的具体实现1:HDFS实现
public class HDFSService implements IService {

    @Override
    public String sayHello() {
        return "Hello HDFSService";
    }

    @Override
    public String getScheme() {
        return "hdfs";
    }
}
// 服务提供商的具体实现2:Local实现
public class LocalService implements IService {
    @Override
    public String sayHello() {
        return "Hello LocalService";
    }

    @Override
    public String getScheme() {
        return "local";
    }
}

准备一个服务的配置文件:META-INF/services/com.fsx.maintest.IService,然后在该文件里书写上实现类们,内容如下:

代码语言:javascript
复制
com.fsx.serviceloader.MyIService // 注意这个是接口
com.fsx.serviceloader.HDFSService
com.fsx.serviceloader.LocalService

main函数测试:

代码语言:javascript
复制
    public static void main(String[] args) {
        // 加载IService下所有的服务
        ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);

        for (IService service : serviceLoader) {
            System.out.println(service.getScheme() + "=" + service.sayHello());
        }
    }

报错:

代码语言:javascript
复制
java.util.ServiceConfigurationError: com.fsx.serviceloader.IService: Provider com.fsx.serviceloader.MyIService could not be instantiated

很显然写了一个接口,而接口是不能够实例化的。注意到上面说了唯一一个强制要求,就是必须能够实例化(有空的构造函数) 因此做修改如下(只写实现类):

代码语言:javascript
复制
com.fsx.serviceloader.HDFSService
com.fsx.serviceloader.LocalService

运行正常,输出如下:

代码语言:javascript
复制
hdfs=Hello HDFSService
local=Hello LocalService

可以看到ServiceLoader可以根据IService把定义的两个实现类找出来,返回一个ServiceLoader的实现,而ServiceLoader实现了Iterable接口,所以可以通过ServiceLoader来遍历所有在配置文件中定义的类的实例。

几个注意事项: 1、文件名称是服务接口类型的完全限定 2、文件内若有多个实现类,每行一个(末尾不要有空格和,等符号) 3、文件必须使用 UTF-8 编码

另外ServiceLoader拿实例提供者是有缓存的,策略如下:

1、服务加载器维护到目前为止已经加载的提供者缓存

2、每次调用 iterator 方法返回一个迭代器,它首先按照实例化顺序生成缓存的所有元素

3、然后以延迟方式查找和实例化所有剩余的提供者,并且依次将每个提供者添加到缓存

4、若清除缓存,可议调用ServiceLoader.reload()方法

ServiceLoader的应用

一、此处以Hadoop的FileSystem为例,它的原理有这么一段:

代码语言:javascript
复制
private static void loadFileSystems() {  
  synchronized(FileSystem.class){  
    if(!FILE_SYSTEMS_LOADED) {  
			
	  // 此处通过ServiceLoader把FileSystem所有的服务提供者都拿出来并且缓存起来了
	  // 这个概念,特别特别像通过配置文件配置Bean一样,类比Spring吧  只是没那么强大而已
      ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem.class); 
      for(FileSystem fs : serviceLoader) {  
        SERVICE_FILE_SYSTEMS.put(fs.getScheme(),fs.getClass());  
      } 
      FILE_SYSTEMS_LOADED= true; 
    } 
  } 
}

可以看到FileSystem会把所有的FileSystem的实现都以scheme和class来cache,之后就从这个cache中取相应的值。

因此,以后可以通过ServiceLoader来实现一些类似的功能,而不用依赖像Spring这样的第三方框架(你让Hadoop去强一来Spring显然是非常不合适的)。

二、另外一个应用是责任链设计模式

责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

其实Spring底层很多都使用了此模式,但今天主要讲讲平时java中的实现。

其中我们熟悉的JDBC驱动加载就是这个例子。java.sql.DriverManager是用来管理负责加载JDBC驱动的,部分源码展示如下:

代码语言:javascript
复制
public class DriverManager {
	...
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
    	,,,
    	ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    	Iterator<Driver> driversIterator = loadedDrivers.iterator();
       try{
       	  // 只要导入了对应的Jar包,项目启动时候都会加载进来,用户其实并不需要再手动去加载了
           while(driversIterator.hasNext()) {
               driversIterator.next();
           }
       } catch(Throwable t) {
       // Do nothing
       }
    }
    ...
}

然后我们此处以导入MySql驱动的Jar为例,它有一个如下文件:

这也就顺便解释了我们前边的一个疑问,其实JDBC驱动我们自己并不需要手动的去Class.forName()去加载了JDK6以后,只要你导入了包,就自动给你加载进去了

另外我还看到有个哥们写的一个例子,也是典型应用,也分享给大家:消灭成堆的分支语句之类责任链模式

其实JDK的实现方案有的时候并不能满足我们的要求,比如我们希望这个配置文件在任意地方怎么办呢? 这里介绍一个方案:借助google开源的AutoService去自助实现(只不过一般都不这么干,Android应用这么用的可能性会大一点) 这样我们的配置文件就可以像Spring配置文件一下,放在几乎任何地方了




ServletContainerInitializer:和web容器相关的启动器

在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。

使用方式:每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作

咋一看,是不是似曾相识呢?对的,思想同JDK的一毛一样,所以说天下技术也是一大抄嘛~~

一般伴随着ServletContainerInitializer一起使用的还有HandlesTypes注解,通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerdeonStartup方法作为参数传入

这里需要说明一点:即使你类上不标注HandlesTypes注解也是ok的。onStartup方法还是会执行,只不过是public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { ... }它的c这个属性值为null而已。

Servlet3.0规范的类
代码语言:javascript
复制
// 全类名:javax.servlet.ServletContainerInitializer
// @since Servlet 3.0
public interface ServletContainerInitializer {
	...
}

ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架.

以SpringMVC举例, servlet3.0之前我们需要在web.xml中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置

原理
  1. ServletContainerInitializer接口的实现类通过java SPI声明自己是ServletContainerInitializer 的provider
  2. 容器启动阶段依据java spi获取到所有ServletContainerInitializer的实现类,然后执行其onStartup方法.(参考类:ContextConfig监听器)
  3. 另外在实现ServletContainerInitializer时还可以通过@HandlesTypes注解定义本实现类希望处理的类型,容器会将当前应用中所有这一类型(继承或者实现)的类放在ServletContainerInitializer接口的集合参数c中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c为null
  4. 这一类实现了SCI(全名:ServletContainerInitializer)的接口,如果做为独立的包发布,在打包时,会在JAR 文件的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中进行注册

SCI(全称 ServletContainerInitializer)则是根据约定的标准,扫描META-INF中包含注册信息的 class 并在启动阶段调用其onStartup

以Tomcat为例,源码解释原理

为了一探究竟,我下载了Tomcat9的源码并且编译运行,然后做如下记录。

首先我们看ContextConfig,它是个LifecycleListener,在Tomcat容器启动的时候会执行~

代码语言:javascript
复制
package org.apache.catalina.startup

public class ContextConfig implements LifecycleListener {
	...
    @Override
    public void lifecycleEvent(LifecycleEvent event) {
    	...
    	// 容器启动前,会执行configureStart配置工作
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        }
        ... 
    }

    protected synchronized void configureStart() {
    	...
        webConfig();
    }

    protected void webConfig() {
    	...
        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
            processServletContainerInitializers();
        }
    }
	
	// 这一步就是Tomcat专门寻找,并且执行Servlet规范中的`ServletContainerInitializer`的所有的实现类的
    /**
     * Scan JARs for ServletContainerInitializer implementations.
     */
    protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
			
			// 此处使用的是Tomcat自己实现的 WebappServiceLoader,它使用方式完全同java.util.ServiceLoader,并且相关规范也一样的
			// 具体使用方式,请参见ServiceLoader的使用步骤
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
			
			// 找到所有的SCI
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            ok = false;
            return;
        }

		// 遍历,逐个处理@HandlesTypes注解~~~
        for (ServletContainerInitializer sci : detectedScis) {
            initializerClassMap.put(sci, new HashSet<Class<?>>());

            HandlesTypes ht;
            try {
            	// 拿到实现类上面标注的@HandlesTypes注解~~~
                ht = sci.getClass().getAnnotation(HandlesTypes.class);
            } catch (Exception e) {
                continue;
            }
			
			// 如过没有标注此注解,就继续处理下一个
            if (ht == null) {
                continue;
            }
			// 标注了注解,但是没有写value值,也继续处理下一个
            Class<?>[] types = ht.value();
            if (types == null) {
                continue;
            }
			
			// types:注解里面所关心的types,
            for (Class<?> type : types) {
                if (type.isAnnotation()) {
                    handlesTypesAnnotations = true;
                } else {
                    handlesTypesNonAnnotations = true;
                }

				// 以该type为key,对应上对应的SCI  最终onStartup()方法里会把对应Class类型放进去的
                Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
                if (scis == null) {
                    scis = new HashSet<>();
                    typeInitializerMap.put(type, scis);
                }
                scis.add(sci);
            }
        }
    }
}

它的执行在StandardContext#startInternal()这个方法,启动的时候会统一调用。


org.apache.jasper.servlet.JasperInitializer就是Tomcat内部的一个初始化器,用于处理支持JSP页面的。我编译源码运行,发现并 报错:并不支持JSP,所以我只能自己手动添加了此注册器。

代码语言:javascript
复制
    protected synchronized void configureStart() {
    	...
        webConfig();
        // 手动添加的
        context.addServletContainerInitializer(new JasperInitializer(), null);
 		...   	
    }

目前本人并不确定是不是Tomcat9默认是不予支持JSP页面的,因为不写页面好久了。希望有知道的可以留言告知我

Tomcat调用SCI的时机

ServletContainerInitializer的调用时机,可能在绝大部分情况下我们都不必要去了解,只需要知道它会调用就成。但是如果我们了解了回调的先后顺序,可以帮助我们快速定位和理解某些奇怪的问题(因为servlet容器除了会回调SCI之外, 还有回调诸如servlet#init方法, listener等)

这里肯定就以最常用的servlet容器:tomcat举例,回调处理步骤总结如下:

  1. 解析web.xml(第一还是先解析web.xml,因为必须要保持向下兼容嘛)
  2. 往ServletContext实例中注入<context-param> 参数
  3. 回调Servlet3.0的ServletContainerInitializers接口实现类
  4. 触发 Listener 事件(beforeContextInitialized, afterContextInitialized); 这里只会触发 ServletContextListener 类型的
  5. 初始化 Filter, 调用其init方法
  6. 加载 启动时即加载的servlet(servlet最靠后加载~~~)
SCI在Spring中的使用,热插拔的实现基石

SpringBoot 也是这样被点燃的(关于Spring Boot和Tomcat嵌入式容器的关系,后面章节很定还会详细分析)

首先看看Spring Boot中有哪些实现类:

这里面最熟悉的莫过于:SpringServletContainerInitializer。SpringServletContainerInitializer类实现了 ServletContainerInitializer接口。这意味着servlet(3.0以上版本)容器启动时,该类被容器自动加载并执行其onStart方法,这样我们就可以启动我们的Spring容器了

这其中还有有一个LogbackServletContainerInitializer我们看起来非常眼熟,Logback是通过该类进行Web环境初始化的。

SpringFactoriesLoader

Spring最为一个最为流行的开源框架,必然就应该有属于它自己的SPI实现。而SpringFactoriesLoader就是它用来实现SPI的法宝。它在Spring Framework中鲜有应用,但是SpringBoot中被广泛的使用,它的自动配置和这息息相关,因此在讲解boot的自动化配置章节的时候,还会提到它。

上面已经对SPI进行了概念介绍,以及对JDK的标注实现:ServiceLoad进行了描述解释。

我们发现很多框架等都有自己对SPI的实现,

比如tomcat的实:WebappServiceLoader它用于比如容器启动时加载所有的ServletContainerInitializer实现类,从而驱动Spring容器的启动。

Spring的实现:SpringFactoriesLoader它用于SpringBoot中的自动化配置起到了关键甚至决定性作用~

注意:**SpringFactoriesLoader**的不同之处在于,它内部都是key-value的形式,这是和前两种SPI不一样的地方

使用Demo

是骡子是马,拉出来溜溜就可以了。

SpringFactoriesLoader**默认加载的路径和文件为:类路径下**META-INF/spring.factories

代码语言:javascript
复制
com.fsx.serviceloader.IService=com.fsx.serviceloader.HDFSService,com.fsx.serviceloader.LocalService

// 若有非常多个需要换行 可以这么写
// 前面是否顶头没关系(Spring在4.x版本修复了这个bug)
com.fsx.serviceloader.IService=\
    com.fsx.serviceloader.HDFSService,\
    com.fsx.serviceloader.LocalService

我们这么测试一下即可:

代码语言:javascript
复制
    public static void main(String[] args) throws IOException {
        List<IService> services = SpringFactoriesLoader.loadFactories(IService.class, Main.class.getClassLoader());
        List<String> list = SpringFactoriesLoader.loadFactoryNames(IService.class, Main.class.getClassLoader());
        System.out.println(list); //[com.fsx.serviceloader.HDFSService, com.fsx.serviceloader.LocalService]
        System.out.println(services); //[com.fsx.serviceloader.HDFSService@794cb805, com.fsx.serviceloader.LocalService@4b5a5ed1]
    }

完美work。

使用小细节:

  • spring.factories内容的key不只能是接口,也可以是抽象类、具体的类。但是有个原则:=后面必须是key的实现类(子类)
  • key还可以是注解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一个注解
  • 文件的格式需要保证正确,否则会返回[](不会报错)
  • =右边必须不是抽象类,必须能够实例化。且有空的构造函数~
  • loadFactories依赖方法loadFactoryNamesloadFactoryNames方法只拿全类名,loadFactories拿到全类名后会立马实例化
  • 此处特别注意loadFactories**实例化完成所有实例后,会调用**AnnotationAwareOrderComparator.sort(result)**排序,所以它是支持**Ordered**接口排序的,这个特点特别的重要。**

SpringFactoriesLoader的版本小变化:Spring4.x的时候该类是个抽象类,且没有缓存。Spring5.x后它变成了final类,并且加入了Map<ClassLoader, MultiValueMap<String, String>> cache缓存

原理简单剖析

因为Spring的这个配置文件和上面的不一样,它的名字是固定的spring.factories,里面的内容是key-value形式,因此一个文件里可以定义N多个键值对。我认为它比源生JDK的SPI是更加灵活些的~

它主要暴露了两个方法:loadFactoriesloadFactoryNames

代码语言:javascript
复制
// @since 3.2
public final class SpringFactoriesLoader {
	...
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	...
	// 核心方法如下:
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			// 读取到资源文件,遍历
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();

			
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				// 此处使用的是URLResource把这个资源读进来~~~
				UrlResource resource = new UrlResource(url);
				
				// 可以看到,最终它使用的还是PropertiesLoaderUtils,只能使键值对的形式哦~~~ 当然xml也是被支持的
				
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
		
					// 使用逗号,分隔成数组,遍历   名称就出来了~~~
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		} catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
}

原理其实蛮简单的,主要是SPI这种设计思想,对开闭原则来说还是非常的重要的。因为有了Spring的SPI,所以SpringBoot的自动配置也就自然而然了

Spring中的应用举例

若你不是Boot环境,Spring Framwork中自己应用极少。此处找到一个Spring内部使用者:CachedIntrospectionResults:它是一个内建的还是很有用的一个类,主要和缓存JavaBean有关,还有BeanInfoFactory。它有这么一句使用:

代码语言:javascript
复制
	/** Stores the BeanInfoFactory instances. */
	private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
			BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());

// 配置文件内容为
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory

// 也就是说`BeanInfoFactory`的实现类默认使用的`ExtendedBeanInfoFactory`

关于JavaBean、内省、BeanInfoFactory、BeanInfo等等基础,各位可以自行学习了解一下~

SPI解决的问题场景描述

在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?

  • 其一:我们可以规范不同的系统调用,也就是传递一个系统标识;
  • 其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理;
  • 其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一个功能的实现,A实现类与B实现类的逻辑一点也不一样,但是目标是一样的,这个时候使用策略类是毋庸置疑的?

从上面三种实现方案来看,高下立判。当然任何事情都不是绝对的,如果你要求速度,第一种方案可谓是最为快速的,但是是最难维护的

上述的问题我们是在API层面进行处理的。

那万一有一天让我们自己设计一套框架,然后让别人直接使用(比如Spring,比如tomcat)?我们该如何处理上述的这个问题呢?答:SPI技术

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • ServiceLoader:服务提供者加载器
    • SPI概念介绍
      • ServiceLoader
        • ServiceLoader使用方式
          • ServiceLoader的应用
          • ServletContainerInitializer:和web容器相关的启动器
            • Servlet3.0规范的类
              • 原理
                • 以Tomcat为例,源码解释原理
                  • Tomcat调用SCI的时机
                    • SCI在Spring中的使用,热插拔的实现基石
                    • SpringFactoriesLoader
                      • 使用Demo
                        • 原理简单剖析
                          • Spring中的应用举例
                            • SPI解决的问题场景描述
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档