溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring Boot @EnableAutoConfiguration解析

發(fā)布時(shí)間:2020-07-13 01:13:42 來源:網(wǎng)絡(luò) 閱讀:909 作者:Java筆記丶 欄目:編程語言

剛做后端開發(fā)的時(shí)候,最早接觸的是基礎(chǔ)的spring,為了引用二方包提供bean,還需要在xml中增加對(duì)應(yīng)的包<context:component-scan base-package="xxx" />?或者增加注解@ComponentScan({ "xxx"})。當(dāng)時(shí)覺得挺urgly的,但也沒有去研究有沒有更好的方式。

直到接觸Spring Boot 后,發(fā)現(xiàn)其可以自動(dòng)引入二方包的bean。不過一直沒有看這塊的實(shí)現(xiàn)原理。直到最近面試的時(shí)候被問到。所以就看了下實(shí)現(xiàn)邏輯。


使用姿勢

講原理前先說下使用姿勢。

在project A中定義一個(gè)bean。

package?com.wangzhi;

import?org.springframework.stereotype.Service;

@Service
public?class?Dog?{
}

并在該project的resources/META-INF/下創(chuàng)建一個(gè)叫spring.factories的文件,該文件內(nèi)容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog

然后在project B中引用project A的jar包。

projectA代碼如下:

package?com.wangzhi.springbootdemo;

import?org.springframework.boot.SpringApplication;
import?org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import?org.springframework.boot.autoconfigure.SpringBootApplication;
import?org.springframework.context.ConfigurableApplicationContext;
import?org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
public?class?SpringBootDemoApplication?{

????public?static?void?main(String[]?args)?{
????????ConfigurableApplicationContext?context?=?SpringApplication.run(SpringBootDemoApplication.class,?args);
????????System.out.println(context.getBean(com.wangzhi.Dog.class));
????}

}

打印結(jié)果:

com.wangzhi.Dog@3148f668

原理解析

總體分為兩個(gè)部分:一是收集所有spring.factoriesEnableAutoConfiguration相關(guān)bean的類,二是將得到的類注冊(cè)到spring容器中。

收集bean定義類

在spring容器啟動(dòng)時(shí),會(huì)調(diào)用到AutoConfigurationImportSelector#getAutoConfigurationEntry

protected?AutoConfigurationEntry?getAutoConfigurationEntry(
????????AutoConfigurationMetadata?autoConfigurationMetadata,
????????AnnotationMetadata?annotationMetadata)?{
????if?(!isEnabled(annotationMetadata))?{
????????return?EMPTY_ENTRY;
????}
????//?EnableAutoConfiguration注解的屬性:exclude,excludeName等
????AnnotationAttributes?attributes?=?getAttributes(annotationMetadata);
????//?得到所有的Configurations
????List<String>?configurations?=?getCandidateConfigurations(annotationMetadata,
????????????attributes);
????//?去重
????configurations?=?removeDuplicates(configurations);
????//?刪除掉exclude中指定的類
????Set<String>?exclusions?=?getExclusions(annotationMetadata,?attributes);
????checkExcludedClasses(configurations,?exclusions);
????configurations.removeAll(exclusions);
????configurations?=?filter(configurations,?autoConfigurationMetadata);
????fireAutoConfigurationImportEvents(configurations,?exclusions);
????return?new?AutoConfigurationEntry(configurations,?exclusions);
}

getCandidateConfigurations會(huì)調(diào)用到方法loadFactoryNames

public?static?List<String>?loadFactoryNames(Class<?>?factoryClass,?@Nullable?ClassLoader?classLoader)?{
????????//?factoryClassName為org.springframework.boot.autoconfigure.EnableAutoConfiguration
		String?factoryClassName?=?factoryClass.getName();
????????//?該方法返回的是所有spring.factories文件中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的類路徑
		return?loadSpringFactories(classLoader).getOrDefault(factoryClassName,?Collections.emptyList());
	}


public?static?final?String?FACTORIES_RESOURCE_LOCATION?=?"META-INF/spring.factories";

private?static?Map<String,?List<String>>?loadSpringFactories(@Nullable?ClassLoader?classLoader)?{
		MultiValueMap<String,?String>?result?=?cache.get(classLoader);
		if?(result?!=?null)?{
			return?result;
		}

		try?{
????????????//?找到所有的"META-INF/spring.factories"
			Enumeration<URL>?urls?=?(classLoader?!=?null??
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION)?:
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result?=?new?LinkedMultiValueMap<>();
			while?(urls.hasMoreElements())?{
				URL?url?=?urls.nextElement();
				UrlResource?resource?=?new?UrlResource(url);
????????????????//?讀取文件內(nèi)容,properties類似于HashMap,包含了屬性的key和value
				Properties?properties?=?PropertiesLoaderUtils.loadProperties(resource);
				for?(Map.Entry<?,??>?entry?:?properties.entrySet())?{
					String?factoryClassName?=?((String)?entry.getKey()).trim();
????????????????????//?屬性文件中可以用','分割多個(gè)value
					for?(String?factoryName?:?StringUtils.commaDelimitedListToStringArray((String)?entry.getValue()))?{
						result.add(factoryClassName,?factoryName.trim());
					}
				}
			}
			cache.put(classLoader,?result);
			return?result;
		}
		catch?(IOException?ex)?{
			throw?new?IllegalArgumentException("Unable?to?load?factories?from?location?["?+
					FACTORIES_RESOURCE_LOCATION?+?"]",?ex);
		}
	}

注冊(cè)到容器

在上面的流程中得到了所有在spring.factories中指定的bean的類路徑,在processGroupImports方法中會(huì)以處理@import注解一樣的邏輯將其導(dǎo)入進(jìn)容器。

public?void?processGroupImports()?{
????for?(DeferredImportSelectorGrouping?grouping?:?this.groupings.values())?{
????????//?getImports即上面得到的所有類路徑的封裝
????????grouping.getImports().forEach(entry?->?{
????????????ConfigurationClass?configurationClass?=?this.configurationClasses.get(
????????????????????entry.getMetadata());
????????????try?{
????????????????//?和處理@Import注解一樣
????????????????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);
????????????}
????????});
????}
}

private?void?processImports(ConfigurationClass?configClass,?SourceClass?currentSourceClass,
			Collection<SourceClass>?importCandidates,?boolean?checkForCircularImports)?{
	...
????//?遍歷收集到的類路徑
????for?(SourceClass?candidate?:?importCandidates)?{
???????...
????????//如果candidate是ImportSelector或ImportBeanDefinitionRegistrar類型其處理邏輯會(huì)不一樣,這里不關(guān)注
?????	//?Candidate?class?not?an?ImportSelector?or?ImportBeanDefinitionRegistrar?->
						//?process?it?as?an?@Configuration?class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(),?candidate.getMetadata().getClassName());
		//?當(dāng)作?@Configuration?處理			
????????processConfigurationClass(candidate.asConfigClass(configClass));
???...
}
????????????
????...
}

可以看到,在第一步收集的bean類定義,最終會(huì)被以Configuration一樣的處理方式注冊(cè)到容器中。

End

@EnableAutoConfiguration注解簡化了導(dǎo)入了二方包bean的成本。提供一個(gè)二方包給其他應(yīng)用使用,只需要在二方包里將對(duì)外暴露的bean定義在spring.factories中就好了。對(duì)于不需要的bean,可以在使用方用@EnableAutoConfigurationexclude屬性進(jìn)行排除。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI