SpringBoot原始碼解析-Bean的載入與自動化配置

吾乃上將軍邢道榮發表於2019-03-19

springboot作為一個基於spring開發的框架,自然也繼承了spring的容器屬性。容器中的bean自然成為了springboot各種功能的基礎。本節就來分析一下springboot如何將各種bean載入進容器中。


開始分析之前首先我們先概覽一下springboot框架究竟載入了多少bean。在main函式中新增如下程式碼,執行。

public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
    }
複製程式碼

不出意外的話,控制檯會列印出上百個bean的名字。雖然我們僅僅只寫了兩個類而已!那麼這些類的載入有何規則呢?相比於spring的xml配置檔案,springboot的自動化配置又是如何實現的?這些都將在本節揭曉。

public ConfigurableApplicationContext run(String... args) {
		        ...
		        //建立ApplicationContext
			context = createApplicationContext();
			...
			//做一些初始化配置
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			...
	}
複製程式碼

首先我們進入SpringApplication的run方法中,在run方法中我們看到和ApplicationContext有關的程式碼一共有4行,第一行建立了ApplicationContext,第二行做了一些初始化配置,第三行呼叫了refresh方法,讀過spring原始碼的話應該知道這個方法包含了ApplicationContext初始化最重要也最大部分的邏輯,所以這行待會會重點分析,最後一行是一個空方法,留著子類覆寫。

	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				...
				}
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
複製程式碼

首先進入create方法,在SpringApplication初始化的時候,我們已經知道了這是一個網路服務,所以這邊建立的類是DEFAULT_SERVLET_WEB_CONTEXT_CLASS類,(org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext) 在這邊直接呼叫了無參建構函式。先進入建構函式看一下做了那些事情。

	public AnnotationConfigServletWebServerApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
複製程式碼

初始化了reader和scanner元件,reader是用來註冊bean的,scanner是用來掃描bean的。這兩個元件初始化的邏輯都不復雜,讀者可以自行理解。但是重點關注一個地方。在reader的建構函式中:

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this(registry, getOrCreateEnvironment(registry));
	}
	
        public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		...
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	
	public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
		registerAnnotationConfigProcessors(registry, null);
	}

	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		...
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		...
	}
複製程式碼

一個ConfigurationClassPostProcessor的bean被注入到了容器中,這個地方留意一下,後面這個bean很重要。

建立完成了之後,我們看一下prepareContext方法

	private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		...
		load(context, sources.toArray(new Object[0]));
		...
	}
複製程式碼

prepareContext方法中,呼叫了一些監聽器,和初始化介面,但是最重要的是load這個方法。load這個方法,將我們main方法的這個類傳入了容器中。這個類上面有一個非常重要的註解SpringBootApplication。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
    }

}
複製程式碼

下面就進入到了最重要的refresh方法,如果讀過《spring原始碼深度解析》這本書的話,這個地方的邏輯應該感到很親切,沒讀過的話強烈建議讀一下,不管spring怎麼發展,基礎還是那些的。

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			...
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

			...
	}
複製程式碼

所以refresh方法中的邏輯我也不多介紹了,直接進入主題。invokeBeanFactoryPostProcessors方法。

	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
		...
	}
	
public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		    ...
		    List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();
			...
	}
複製程式碼

在invokeBeanFactoryPostProcessors方法中,從容器中獲取了BeanDefinitionRegistryPostProcessor型別的類,然後執行了這些類的postProcessBeanDefinitionRegistry方法。還記得上面我讓你們重點關注的ConfigurationClassPostProcessor麼,他就是實現了BeanDefinitionRegistryPostProcessor,所以這個地方會呼叫ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。那麼我們進入方法瞧瞧。

	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		...
		processConfigBeanDefinitions(registry);
	}
	
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			...
			//判斷@Configuration註解
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		...
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
		//解析帶有@Configuration註解的類
			parser.parse(candidates);
		...
	}
複製程式碼

processConfigBeanDefinitions方法主要有兩個邏輯,首先判斷類上是否帶有@Configuration註解,然後解析該類。其實在這兒,主要解析的就是@SpringBootApplication註解。因為點開@SpringBootApplication註解的原始碼

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@Configuration
public @interface SpringBootConfiguration {

}
複製程式碼

@SpringBootApplication註解上面有@SpringBootConfiguration註解,而後者又包含了@Configuration註解,所以這個地方,解析的就是帶有@SpringBootApplication註解的類。進入parse方法。

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
		...

		this.deferredImportSelectorHandler.process();
	}
複製程式碼

主要有兩個邏輯,我們一個一個來分析。首先再次進入parse方法

	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName));
	}

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		...
		SourceClass sourceClass = asSourceClass(configClass);
		do {
		//進入這個方法
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}
複製程式碼

在doProcessConfigurationClass中,我們看到了熟悉的Component,PropertySources,ComponentScan,ImportResource,以及Import註解,上述幾個註解的功能大家應該都很熟悉了,我就不多介紹了,這些註解在這兒就完成了他們的使命,經過這個方法後,我們自己寫的類就會全部進入springboot容器中了。

下面開始分析this.deferredImportSelectorHandler.process();

public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
				...
		}
複製程式碼

進入方法後發現如果deferredImportSelectors為空的話,就什麼都做不了。但是呼叫debug後發現這個地方是有值的,那麼他是什麼時候被放進來的呢。我們回頭看剛剛的doProcessConfigurationClass方法。

	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
		...
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);
		...
	}
	
	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		...
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(
									configClass, (DeferredImportSelector) selector);
						...
		}
	}

		public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			...
				this.deferredImportSelectors.add(holder);
			}
		}
複製程式碼

在processImports發現了新增的痕跡。但是新增有個前提條件是要import匯入的類selector instanceof DeferredImportSelector,這個條件是怎麼實現的呢?答案就在@SpringBootApplication註解中。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
複製程式碼

所以到這兒我們就知道了deferredImportSelectors裡面有一個元素,就是這邊的AutoConfigurationImportSelector。

所以到這兒,我們就可以接著分析process方法了

		public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					//註冊
					deferredImports.forEach(handler::register);
					//解析
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
複製程式碼

一個註冊方法,一個解析方法,註冊方法邏輯比較簡單,我們直接進入解析方法。

		public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
			//這個地方看一下getImports方法
				grouping.getImports().forEach(entry -> {
					...
					//這個方法標記一下,processImport待會回來
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					...
			}
		}

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
			//重點看process方法
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

		public void process(AnnotationMetadata annotationMetadata,
				DeferredImportSelector deferredImportSelector) {
			...
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
							annotationMetadata);
			...
		}

	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		...
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		...
	}

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		...
		return configurations;
	}

複製程式碼

SpringFactoriesLoader.loadFactoryNames這個方法熟悉麼,一直在用,所以話不多說,先看看getSpringFactoriesLoaderFactoryClass返回了一個什麼類。返回的是EnableAutoConfiguration.class; 所以進入配置檔案檢視。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
...
...
...
複製程式碼

你應該會看到這麼長長的一串配置,這裡就是springboot自動化配置的中心了。我就以aop來展示一下springboot是如何簡化spring的配置的。

首先經過我們剛剛的一串邏輯org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,這個類會被載入進容器中,那麼這個類,和aop又有啥關係呢。

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}
複製程式碼

檢視該類的原始碼,發現該類載入時有兩個判斷條件,容器中需要有EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class這幾個註解,或者有spring.aop相關的配置。(關於Conditional條件的機制後面再詳細解讀,這個地方大概瞭解一下即可)

如果我們在啟動時的類上新增了EnableAspectJAutoProxy註解的話,該註解會載入AspectJAutoProxyRegistrar類,這個類又會向容器注入AnnotationAwareAspectJAutoProxyCreator類,而後者正是aop的核心類。只要這個類進入容器,容器就帶有了aop功能(aop如何實現的看我推薦的那本書,書上很詳細)。

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
複製程式碼

那麼如果我沒有顯示的新增EnableAspectJAutoProxy註解會怎樣呢?如果沒有顯示新增的話,只要滿足其他條件,AopAutoConfiguration類依然會被載入進容器,而他進入容器後,裡面得到兩個靜態類也會被掃描進容器,而這兩個類都是帶有EnableAspectJAutoProxy註解的,所以aop功能依然可以實現。

所以當我們獲得了自動化配置的這些支援後,就該回到剛剛標記的processImport方法了。

		public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
					//剛剛標記的方法
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}
複製程式碼

這個方法會把我們獲得的自動化配置相關支援全部匯入容器,這樣在經過spring那一套載入邏輯之後,我們的springboot專案就可以獲得各種我們配置的功能了。


返回目錄

相關文章