Java Springboot自動裝配原理詳解

Debug路線圖

說多都是淚,大傢看圖。

在這裡插入圖片描述

讓我們從run說起

用瞭這麼多年的的Springboot,這個 run() 方法到底做瞭些什麼事呢?

@SpringBootApplication
public class SpringbootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}

歸屬

run() 方法歸屬於 SpringApplication.class 對象,所以在調用run() 方法前,需要先實例化 SpringApplication.class 對象:

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

SpringApplication.class 對象實例化時,都做瞭些什麼事呢?

這裡主要看需要註意兩個方法:①getSpringFactoriesInstances() 和 ②deduceMainApplicationClass()

/**
 * 實例化時,實際調用的構造方法
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 這裡將spring.
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// 設置初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 設置監聽器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

getSpringFactoriesInstances()方法主要加載整個應用程序中的 spring.factories 文件,將文件的內容放到緩存對象中,方便後續獲取使用。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// 初次加載,cache中獲取不到數據,所以為null
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		result = new HashMap<>();
		try {
			// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",這個配置類眼熟吧
			// 加載配置類
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 這裡加載資源
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}
			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			// 這裡將獲取到資源放入cache中
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

deduceMainApplicationClass() 方法返回瞭啟動類的類信息:

在這裡插入圖片描述

小結

實例化SpringApplication.class 對象完成瞭兩件事:

1.加載整個應用程序中的 spring.factories 文件,將文件的內容放到緩存對象中,方便後續獲取使用。

2.返回瞭啟動類的類信息。

run

有瞭SpringApplication.class 對象實例對象,接下來就可以調用run() 方法。

在run() 方法中,我們主要關註兩個方法prepareContext() 和 refreshContext()

public ConfigurableApplicationContext run(String... args) {
// 省略部分代碼
	try {
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
	}
// 省略部分代碼

prepareContext()方法準備上下文環境,通過調用load()方法加載啟動類,為獲取啟動類上的註解做準備;

private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		// 這裡會判斷啟動類不是一個groovy閉包也不是一個匿名類
		if (isEligible(source)) {
			// 註冊讀取啟動類的註解信息
			// 註意,這裡將啟動類型註冊為AnnotatedBeanDefinition類型,後面parse()解析時會用到。
			this.annotatedReader.register(source);
		}
	}

refreshContext() 方法最終調用瞭AbstractApplicationContext.class 類的 refresh(),這裡相信看過spring源碼的小夥伴都很熟悉 refresh() 這個方法。

自動裝配操作的主戰場主要是在 ①invokeBeanFactoryPostProcessors() 方法,①調用瞭②invokeBeanDefinitionRegistryPostProcessors() 方法,②調用瞭ConfigurationClassPostProcessor.class 的③postProcessBeanDefinitionRegistry()方法,③調用瞭 ④processConfigBeanDefinitions() 方法;

④ processConfigBeanDefinitions() 方法中:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 這裡會循環匹配到啟動類,並且添加到上面的configCandidates集合中。
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
		// ...
		
		// 解析每一個標註瞭@Configuration註解的類,啟動類上的@SpringBootApplication就包含瞭@Configuration註解
		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 {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			// 開始解析
			parser.parse(candidates);
			parser.validate();
	}
		// ============ 分割線 =================
/**
 * 為瞭方便閱讀,這裡將parse()方法接入
 */
public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				// 在前面prepareContext()方法中的load()方法中已經說過,所以這會進入這個判斷解析
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}	
		// ...
		}
		// 註意!!這裡,parse()解析完成後,會回到這裡執行process()方法;
		this.deferredImportSelectorHandler.process();
	}

進入判斷後parse()方法會接著調用 ①processConfigurationClass()方法,①調用②doProcessConfigurationClass()方法;

doProcessConfigurationClass()中又開始對註解進行進一步的解析,包括@PropertySource、@ComponentScan、@Import(咱們看這個)、@ImportResource、@Bean,解析之前,會通過getImports()方法調用collectImports()方法,統計出被@Import標註的類型信息;

	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// ...
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		// ...
// ============ 分割線 =================
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					// 註意看這裡,自調用遞歸查詢
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}
}

getImports() 方法查詢結果展示:

在這裡插入圖片描述

parse()方法解析完成 @Import 註解後(這裡忘記的小夥伴,可看看上面的parse()方法,我有代碼註釋),接著開始調用①process()方法,①中調用②processGroupImports()方法,②中接著調用 ③grouping.getImports()方法,③調用DeferredImportSelector.Group 接口的 ④process()方法,這裡我們看它的實現類 AutoConfigurationImportSelector.class 實現的 ④process()方法(這裡需要留意一下,一會還會回來用到),④調用瞭 ⑤getAutoConfigurationEntry(),⑤中調用瞭⑥getCandidateConfigurations() 方法;

重點來瞭:經過上面的一系列方法調用,終於來到這個方法,相信大傢在許多博客裡都又看到過 ⑥getCandidateConfigurations() 這個方法,但又沒有說清楚具體是怎麼調用到這個方法的,隻是說瞭這個類會得到待配置的class的類名集合等等;

在⑥這個方法中,顯示通過 ⑦getSpringFactoriesLoaderFactoryClass() 這個方法返回瞭一個EnableAutoConfiguration.class 註解對象,然後又通過調用 ⑧loadFactoryNames(),⑧又調用瞭 ⑨loadSpringFactories();

⑧⑨方法看著是不是比較眼熟? 是的,我們在初始化SpringApplication對象時,曾調用過這兩個方法,在調用⑨時,將 spring.factories 文件的內容放到cache緩存對象中。

@Override
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// getSpringFactoriesLoaderFactoryClass()這個方法返回瞭一個EnableAutoConfiguration.class註解對象
		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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
}
// ===========================分割線===========================
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
	// ...

此時的cache對象中存在EnableAutoConfiguration對象,size=131個:

在這裡插入圖片描述

這131個就是 spring.factories 文件中的自動裝配配置項:

在這裡插入圖片描述

當然,這裡面有許多我們沒有用到類信息也被裝配瞭進來,這裡不要著急接著往下看,裝配完成後回到瞭⑤getAutoConfigurationEntry()方法中,且返回瞭一個List< String>的一個配置類信息集合,接著又做瞭些什麼事?

在這裡插入圖片描述

從上圖可以看出,131個配置信息,經過過濾移除後,最終變成13個需要使用的,拿到最終配置信息,(愣著幹嘛,趕緊撒花呀!),到這裡自動裝配過程基本上就結束瞭。

這裡的結束是指自動裝配過程結束,也就是我們 refresh()中的invokeBeanFactoryPostProcessors() 方法執行結束,當然這個方法還做很多別的事,但是本文隻關註自動裝配相關,完成此方法後並不表示類就已經實例化完成,這裡隻是將類信息裝配到瞭spring容器中,後續會有別的方法完成類的實例化。(實例化看它:finishBeanFactoryInitialization())

在這裡插入圖片描述

再說說註解

@SpringBootApplication 是的沒錯,這個註解大傢都熟悉,springboot 項目啟動類上都有:

在這裡插入圖片描述

@SpringBootApplication 中包含瞭兩個註解:

  • @EnableAutoConfiguration(重點):啟用 SpringBoot 的自動配置機制;
  • @ComponentScan: 掃描被@Component (@Service,@Controller)註解的 bean,註解默認會掃描該類所在的包下所有的類;
  • @SpringBootConfiguration:允許在上下文中註冊額外的 bean 或導入其他配置類;

三個註解中,自動裝配的核心 @EnableAutoConfiguration 就是這個註解:

在這裡插入圖片描述

@EnableAutoConfiguration 註解通過 Spring 提供的 @Import 註解導入瞭 AutoConfigurationImportSelector.class類(@Import 註解可以導入配置類或者 Bean 到當前類中),這個類的作用在上也說過(獲取spring.factories文件中待配置的class的類名集合)。

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: