“这篇对Spring SPI和自动装配原理的分析,来源于一次自己开发spring boot starter的经历,带着对这种扩展机制的好奇来看相关的源码的时候越发觉得Spring或SpringBoot虽然天天用,但真的是所谓灯下黑,作为一个胶水式的基础整合框架,Spring要做到足够通用和抽象以及强扩展性,所以用了大量设计模式做所谓解耦,类的数量巨多,Spring的原理和作用说说可能谁都会,但是源码真的是笔者觉得最难读的之一,相比一些专注于某个特定功能的框架要难多了。本文是笔者硬着头皮看了源码和debug之后的一些理解,难免有不当的地方,而且一些个细节笔者也没有去深究,只是把主要的一些个点和思路串起来了留了一些印象。所以本文仅供大家参考。”
Java 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 {};
}
@AutoConfigurationPackage
里边是@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class)
"这里有点跳跃",从主类main方法怎么执行,然后走到注解这的,又是怎么执行到的
AutoConfigurationImportSelector.getCandidateConfigurations()
方法。
这里涉及到springboot的自动装配原理,最终是springboot启动时,是将主类作为一个配置类,ConfigurationClassPostProcessor.processConfigBeanDefinitions,然后通过ConfigrationClassParser类parse()、getImports()解析到@Import注解、从而获取到AutoConfigurationImportSelector这个类的,最终会调用到getCandidateConfiguration()方法。 完整的调用可以看下面的线程栈:
Thread [main] (Suspended)
owns: Object (id=69)
SpringFactoriesLoader.loadSpringFactories(ClassLoader) line: 128
SpringFactoriesLoader.loadFactoryNames(Class>, ClassLoader) line: 122
AutoConfigurationImportSelector.getCandidateConfigurations(AnnotationMetadata, AnnotationAttributes) line: 171
AutoConfigurationImportSelector.getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) line: 116
AutoConfigurationImportSelector$AutoConfigurationGroup.process(AnnotationMetadata, DeferredImportSelector) line: 396
ConfigurationClassParser$DeferredImportSelectorGrouping.getImports() line: 869
ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports() line: 798
ConfigurationClassParser$DeferredImportSelectorHandler.process() line: 770
ConfigurationClassParser.parse(Set) line: 185
ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry) line: 315
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) line: 232
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(Collection, BeanDefinitionRegistry) line: 275
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List) line: 95
AnnotationConfigApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 705
AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 531
SpringApplication.refresh(ApplicationContext) line: 744
SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391
SpringApplication.run(String...) line: 312
SpringApplication.run(Class>[], String[]) line: 1215
SpringApplication.run(Class>, String...) line: 1204
TestRestfullApplication.main(String[]) line: 18
总之,我们来到了getCandidateConfigurations
方法:
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)
ps:eclipse里边debug看是sun.misc.Launcher$AppClassLoader
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去掉也是可以注入的。
从META-INF/spring.factories文件里读取配置的自动装配Bean,EnableAutoConfiguration=xxx,然后这些Bean实例化的也是在springboot启动的时候完成的。对应main线程的完整线程栈:
Thread [main] (Suspended)
owns: ConcurrentHashMap (id=203)
owns: Object (id=34)
BeanUtils.instantiateClass(Constructor, Object...) line: 171
CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory) line: 87
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateBean(String, RootBeanDefinition) line: 1294
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBeanInstance(String, RootBeanDefinition, Object[]) line: 1196
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 555
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 515
DefaultListableBeanFactory(AbstractBeanFactory).lambda$doGetBean$0(String, RootBeanDefinition, Object[]) line: 320
1932470703.getObject() line: not available
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory>) line: 222
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class, Object[], boolean) line: 318
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 199
DefaultListableBeanFactory.preInstantiateSingletons() line: 847
AnnotationConfigApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 877
AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 549
SpringApplication.refresh(ApplicationContext) line: 744
SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391
SpringApplication.run(String...) line: 312
SpringApplication.run(Class>[], String[]) line: 1215
SpringApplication.run(Class>, String...) line: 1204
TestRestfullApplication.main(String[]) line: 18
最终是来到BeanUtils.instantiateClass(Constructor
public static T instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
}
catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
}
catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}
注意上面代码里ctor.newInstance(args)
就是利用Java反射实例化Bean对象了。
我们比较一下上面两个线程栈的信息可以发现都会走到AbstractApplicationContext.refresh()
这个方法里,所不同的是接下在这个方法内又去调用两个不同的方法:加载spring.factories调用的是invokeBeanFactoryPostProcessors
,实例化Bean调用的是finishBeanFactoryInitialization
,所以有必要来看一下这个refresh()方法,有种说法是读懂了refresh()方法就掌握了Spring容器的启动逻辑。
传说中的refresh()方法
org.springframework.context.support
包下的
AbstractApplicationContext
类的refresh()
方法是整个spring框架启动逻辑的“大纲”,refresh里边调的15个方法体现了容器中Bean的整个创建过程。比如前面starter里边配置的Bean,SpringBoot如何读取的spring.factories文件拿到类名,然后创建Defination,到最后使用反射进行实例化Bean,都在refresh方法中有所体现。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // spring.factories文件里的配置是在这加载的
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); //对配置的bean进行反射实例化
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
总结
springboot是基于Spring框架开发的,利用引导主类作为总的配置入口和启动入口,使用它自己上边的注解,然后处理@Import注解、拿到里边的AutoConfigurationImportSelector
、调用它的相应的方法来加载SPI插件的配置spring.factories,之后使用反射来实例化插件中的Bean供使用。
参考
https://www.cnblogs.com/niechen/p/9027804.html