本文章主要想了解一下SpringBoot,Seata组件中都是怎么使用SPI功能。如何调用SPI的接口实现类,能够提供哪些便利。
SPI (Service Provider Interface) -- 接口服务发现机制
Spring SPI
SpringBoot中最方便的功能莫过于自动配置功能,只需要集成一个Starter的jar包。基本不需要配置就可以实现数据库调用,远程调用,服务配置,服务发现等功能。Spring其实也是使用自己实现的SPI机制来进行的初始化加载并自动配置。
源码解析
Spring的加载都是通过调用SpringApplication.run
来进行处理的。追踪一下这个注解,通过run方法可以很容易找到入口操作的地方。
这里就是部分的SPI入口,可以得知是加载了两个接口实现类,分别为ApplicationContextInitializer(应用上下文初始化类),ApplicationListener(应用监听类)
分为两步,第一步获取所有需要加载的SPI的全限定名称(使用Set来进行去重)。第二步利用BeanUtils.instantiateClass
方法实例化这些对象。
获取需要加载的类
追踪后重点看一下这个箭头这里加载的路径,从资源路径加载和从系统路径加载。路径值为
这里就可以得到所有的SPI类都会放到上述路径中供spring加载。这里其实可以延伸一个问题,刚才只是加载了两个接口的类,Spring也实现其他接口的类。上面可以看到有一个8 usages
,这里就可以知道调用的地方其实很多,对应其他的SPI实现接口类的加载。
ApplicationContextInitializer | 应用上下文初始化类 |
---|---|
ApplicationListener | 应用监听类 |
SpringApplicationRunListener | Spring应用执行监听 |
EnableAutoConfiguration(注解) | 大部分自动配置类(invokeBeanFactoryPostProcessors(beanFactory);) |
AutoConfigurationImportFilter | 自动配置过滤类 |
AutoConfigurationImportListener | 自动配置监听类 |
EnvironmentPostProcessor | 环境变量执行器类 |
PropertySourceLoader | 属性加载器 |
加粗的是比较常用的接口加载类,当需要使用对应功能时,直接在自己的Jar包中实现对应的接口,并配置到META-INF/spring.factories
中即可。spring就可以读到对应的配置了。
常见用例
打开Mybatis Starter中的META-INF/spring.factories
,可以看到
这里有一个概念需要强化一下,类上面添加了注解Configuration
的类就不是一个普通的spring bean,具备了一定的配置功能。由ConfigurationClassParser
中的parse
方法来进行处理。类似于以前spring的一个xml文件,可以加载很多的bean。查看mybatis的配置类可以知道mybatis初始化了sqlSessionFactory
,sqlSessionTemplate
,spring相应的会自动配置这些bean到容器中。
Seata SPI
照例打开META-INF
文件可以看到
在services文件夹下有多个文件,文件名称代表接口名称,文件内容是代表接口的多个实现。搜索全局代码后找到入口类时EnhancedServiceLoader
(增强服务加载类)。核心的逻辑在内部类InnerEnhancedServiceLoader
中。相应的也是有缓存机制,如果已经加载的类直接后去,SEATA的这个SPI使用静态方法实现,比较容易接入系统。
接着往下找,加载路径类的逻辑写在findAllExtensionDefinition
中,分别加载了META-INF/services
和META-INF/seata
中的实现类。代码里面可以找到很多参照spring的逻辑,加载的类也分为SINGLETON
和PROTOTYPE
,同样也是采用DefinitionHolder来进行类的初始化等。
类的对象创建是在getExtensionInstance
中实现,利用构造器创建的对象。整体看下来seata的SPI适配性更好,使用静态方法加载,且支持对象列表返回。
可以看到调用的地方是直接调用静态方法,传入想要的方法即可。
其实综合上面两种SPI机制,可以知道SPI是为扩展准备的,当需要适配不同类型的插件,且需要动态加载时。就可以采用SPI机制来进行加载自实现的接口实现类。