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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM