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 {};

}

@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<BeanDefinitionHolder>) line: 185	
ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry) line: 315	
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) line: 232	
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(Collection<BeanDefinitionRegistryPostProcessor>, BeanDefinitionRegistry) line: 275	
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List<BeanFactoryPostProcessor>) 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<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> 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<String, List<String>>里边,
按照name=EnableAutoConfiguration获取list,如果没有值就返回个空的list。
*/
loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

loadSpringFactories方法很有意思,从spring.factories文件加载里边的k-v,然后一个key可能会有多个逗号分隔的value,所以这里最后返回的是个LinkedMultiValueMap。加载的时候会建立一个cache,下次就不用重复从文件里加载了直接读cache:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //根据classLoader查cache这个map如果查到了说明factories已经加载过了,直接从缓存返回就行了
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	//所以下面的逻辑就是怎么填充这个cache
	try {
        //用自定义classloader从Resource加载,没有就用system ClassLoader
		Enumeration<URL> 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<K,V>  (id=203)	
owns: Object  (id=34)	
BeanUtils.instantiateClass(Constructor<T>, 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<T>, 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<T>, Object...)

public static <T> T instantiateClass(Constructor<T> 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

posted on 2021-11-20 11:59  肥兔子爱豆畜子  阅读(4876)  评论(0编辑  收藏  举报

导航