SpringBoot的SPI机制
Java中自带了所谓SPI机制,按照约定去META-INF/services目录里找各个接口的配置文件,找到接口的实现类,然后使用当前线程上线文类加载器定位到实现类加载器,通过其加载实现类,然后再反射newInstance得到实现类的实例。
Spring里也有类似的SPI,思路根上面类似,从classpath下所有jar包的META-INF/spring.factories 配置文件中加载标识为EnableAutoConfiguration的配置类,然后将其中定义的bean注入到Spring容器。
笔者认为,跟Java的SPI更多的是为了面向接口编程和克服双亲委派局限不同,Spring的这种SPI可能更多的是体现一种框架的可扩展性:在springboot工程中我们都知道,默认是会加载主类所在目录及其所有子目录下的自动注入bean的,比如主类在
com.wangan,则com.wangan.controller,com.wangan.service等等都会加载并注入;但如果第三方开发的jar包、大概率情况下目录是跟工程目录不同的比如wangan公司的合作伙伴,lb公司开发了一个组件用的是com.lb.*,这个组件的类就没法自动的注入到spring,而通过上面讲的SPI机制就可以解决这个问题。
按照上面的思路,我们从spring boot工程的主类开始分析一下相关的源代码:
源代码走读
@SpringBootApplication实际上由三个注解组成:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@SpringBootConfiguration查看代码就是个@Configuration,所以上面的3注解相当于就是@EnableAutoConfiguration,@Configuration,@ComponentScan
先研究下@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class)
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
SpringFactoriesLoader的方法
loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader() )
两个参数,前一个就是EnableAutoConfiguration.class, 后一个getBeanClassLoader()是spring bean的classLoader(这个跟整个工程deploy有关,最后通过gradle打出来的jar包解压出来,有个目录专门放的就是classLoader,应该是一个自定义的classLoader,稍后研究一下)
loadFactoryNames调的是loadSpringFactories,
/*
从loadSpringFactories返回的 Map>里边,
按照name=EnableAutoConfiguration获取list,如果没有值就返回个空的list。
*/
loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
loadSpringFactories方法很有意思,从spring.factories文件加载里边的k-v,然后一个key可能会有多个逗号分隔的value,所以这里最后返回的是个LinkedMultiValueMap。加载的时候会建立一个cache,下次就不用重复从文件里加载了直接读cache:
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
//根据classLoader查cache这个map如果查到了说明factories已经加载过了,直接从缓存返回就行了
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
//所以下面的逻辑就是怎么填充这个cache
try {
//用自定义classloader从Resource加载,没有就用system ClassLoader
Enumeration urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) { //遍历从spring.factories查到的k-v,文件里可能是一个key多个v,逗号分隔
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
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) { //抛异常,说从META-INF/spring.factories加载不了
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
好了,到此我们分析完了getCandidateConfigurations这条线的关键代码逻辑了,总结一下就是从spring.factories里边加载配置的那些key-values,然后找到里边key名字是是EnableAutoConfiguration的那些。
嗯,现在知道哪些类要(作为springboot插件)自动装配了,但是什么时候装配的呢?
getCandidateConfigurations所在的类AutoConfigurationImportSelector实现了DeferredImportSelector接口,然后在ConfigurationClassPostProcessor这条线中,解析parse我们的各个@Configuration注解的类的时候,会去processDeferredImportSelectors(),在这个方法里进行的bean的生成。从实验结果上来看是spring.factories配置了EnableAutoConfiguration类型的那些个类,无论用的注解是@Component、@Configuration还是@RibbonClients都给加载了,然后生成了spring bean注入到了上下文里。进一步实验可知,即使把@Component去掉也是可以注入的。
参考: