欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java Springboot自動裝配原理詳解

 更新時間:2021年10月08日 16:13:06   作者:zhuzicc  
這篇文章主要介紹了詳解SpringBoot自動配置原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

Debug路線圖

說多都是淚,大家看圖。

在這里插入圖片描述

讓我們從run說起

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

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

歸屬

run() 方法歸屬于 SpringApplication.class 對象,所以在調(diào)用run() 方法前,需要先實(shí)例化 SpringApplication.class 對象:

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

SpringApplication.class 對象實(shí)例化時,都做了些什么事呢?

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

/**
 * 實(shí)例化時,實(shí)際調(diào)用的構(gòu)造方法
 */
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();
		// 設(shè)置初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 設(shè)置監(jiān)聽器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

getSpringFactoriesInstances()方法主要加載整個應(yīng)用程序中的 spring.factories 文件,將文件的內(nèi)容放到緩存對象中,方便后續(xù)獲取使用。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// 初次加載,cache中獲取不到數(shù)據(jù),所以為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() 方法返回了啟動類的類信息:

在這里插入圖片描述

小結(jié)

實(shí)例化SpringApplication.class 對象完成了兩件事:

1.加載整個應(yīng)用程序中的 spring.factories 文件,將文件的內(nèi)容放到緩存對象中,方便后續(xù)獲取使用。

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

run

有了SpringApplication.class 對象實(shí)例對象,接下來就可以調(diào)用run() 方法。

在run() 方法中,我們主要關(guān)注兩個方法prepareContext() 和 refreshContext()

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

prepareContext()方法準(zhǔn)備上下文環(huán)境,通過調(diào)用load()方法加載啟動類,為獲取啟動類上的注解做準(zhǔn)備;

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() 方法最終調(diào)用了AbstractApplicationContext.class 類的 refresh(),這里相信看過spring源碼的小伙伴都很熟悉 refresh() 這個方法。

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

④ processConfigBeanDefinitions() 方法中:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 這里會循環(huán)匹配到啟動類,并且添加到上面的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));
			}
		}
		
		// ...
		
		// 解析每一個標(biāo)注了@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()方法中已經(jīng)說過,所以這會進(jìn)入這個判斷解析
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}	
		// ...
		}
		// 注意??!這里,parse()解析完成后,會回到這里執(zhí)行process()方法;
		this.deferredImportSelectorHandler.process();
	}

進(jìn)入判斷后parse()方法會接著調(diào)用 ①processConfigurationClass()方法,①調(diào)用②doProcessConfigurationClass()方法;

doProcessConfigurationClass()中又開始對注解進(jìn)行進(jìn)一步的解析,包括@PropertySource、@ComponentScan、@Import(咱們看這個)、@ImportResource、@Bean,解析之前,會通過getImports()方法調(diào)用collectImports()方法,統(tǒng)計出被@Import標(biāo)注的類型信息;

	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())) {
					// 注意看這里,自調(diào)用遞歸查詢
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}
}

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

在這里插入圖片描述

當(dāng)parse()方法解析完成 @Import 注解后(這里忘記的小伙伴,可看看上面的parse()方法,我有代碼注釋),接著開始調(diào)用①process()方法,①中調(diào)用②processGroupImports()方法,②中接著調(diào)用 ③grouping.getImports()方法,③調(diào)用DeferredImportSelector.Group 接口的 ④process()方法,這里我們看它的實(shí)現(xiàn)類 AutoConfigurationImportSelector.class 實(shí)現(xiàn)的 ④process()方法(這里需要留意一下,一會還會回來用到),④調(diào)用了 ⑤getAutoConfigurationEntry(),⑤中調(diào)用了⑥getCandidateConfigurations() 方法;

重點(diǎn)來了:經(jīng)過上面的一系列方法調(diào)用,終于來到這個方法,相信大家在許多博客里都又看到過 ⑥getCandidateConfigurations() 這個方法,但又沒有說清楚具體是怎么調(diào)用到這個方法的,只是說了這個類會得到待配置的class的類名集合等等;

在⑥這個方法中,顯示通過 ⑦getSpringFactoriesLoaderFactoryClass() 這個方法返回了一個EnableAutoConfiguration.class 注解對象,然后又通過調(diào)用 ⑧loadFactoryNames(),⑧又調(diào)用了 ⑨loadSpringFactories();

⑧⑨方法看著是不是比較眼熟? 是的,我們在初始化SpringApplication對象時,曾調(diào)用過這兩個方法,在調(diào)用⑨時,將 spring.factories 文件的內(nèi)容放到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 文件中的自動裝配配置項:

在這里插入圖片描述

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

在這里插入圖片描述

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

這里的結(jié)束是指自動裝配過程結(jié)束,也就是我們 refresh()中的invokeBeanFactoryPostProcessors() 方法執(zhí)行結(jié)束,當(dāng)然這個方法還做很多別的事,但是本文只關(guān)注自動裝配相關(guān),完成此方法后并不表示類就已經(jīng)實(shí)例化完成,這里只是將類信息裝配到了spring容器中,后續(xù)會有別的方法完成類的實(shí)例化。(實(shí)例化看它:finishBeanFactoryInitialization())

在這里插入圖片描述

再說說注解

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

在這里插入圖片描述

@SpringBootApplication 中包含了兩個注解:

  • @EnableAutoConfiguration(重點(diǎn)):啟用 SpringBoot 的自動配置機(jī)制;
  • @ComponentScan: 掃描被@Component (@Service,@Controller)注解的 bean,注解默認(rèn)會掃描該類所在的包下所有的類;
  • @SpringBootConfiguration:允許在上下文中注冊額外的 bean 或?qū)肫渌渲妙悾?/li>

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

在這里插入圖片描述

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

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • RestTemplat中關(guān)于getForobject方法的使用

    RestTemplat中關(guān)于getForobject方法的使用

    這篇文章主要介紹了RestTemplat中關(guān)于getForobject方法的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 深入淺出的學(xué)習(xí)Java ThreadLocal

    深入淺出的學(xué)習(xí)Java ThreadLocal

    本文會基于實(shí)際場景介紹ThreadLocal如何使用以及內(nèi)部實(shí)現(xiàn)機(jī)制。 具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • SpringBoot AOP處理請求日志打印功能代碼實(shí)例

    SpringBoot AOP處理請求日志打印功能代碼實(shí)例

    這篇文章主要介紹了SpringBoot AOP處理請求日志打印功能代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • java?9大性能優(yōu)化經(jīng)驗總結(jié)

    java?9大性能優(yōu)化經(jīng)驗總結(jié)

    這篇文章主要介紹了java?9大性能優(yōu)化經(jīng)驗總結(jié),包括:Java代碼優(yōu)化,數(shù)據(jù)庫優(yōu)化,分布式緩存,異步化,Web前段,搜索引擎優(yōu)化等需要的朋友可以參考下
    2023-02-02
  • java中拼接字符串的5種方法效率對比

    java中拼接字符串的5種方法效率對比

    這篇文章主要給大家介紹了關(guān)于java中拼接字符串的5種方法效率對比的相關(guān)資料,文中通過示例代碼和圖片介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • java @Value(

    java @Value(

    這篇文章主要介紹了java @Value("${}")獲取不到配置文件中值的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java微信公眾平臺之素材管理

    Java微信公眾平臺之素材管理

    這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺之素材管理,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • spring mvc中@RequestBody注解的作用說明

    spring mvc中@RequestBody注解的作用說明

    這篇文章主要介紹了spring mvc中@RequestBody注解的作用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Spring Boot快速入門教程

    Spring Boot快速入門教程

    本篇文章主要介紹了Spring Boot快速入門教程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • java中的DateTime的具體使用

    java中的DateTime的具體使用

    本文主要介紹了java中的DateTime的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02

最新評論