前言
????????最近在學(xué)習(xí)Spring Boot相關(guān)的課程,過程中以筆記的形式記錄下來,方便以后回憶,同時(shí)也在這里和大家探討探討,文章中有漏的或者有補(bǔ)充的、錯(cuò)誤的都希望大家能夠及時(shí)提出來,本人在此先謝謝了!
開始之前呢,希望大家?guī)е鴰讉€(gè)問題去學(xué)習(xí):
1、Spring Boot 自動(dòng)裝配是什么?
2、這個(gè)功能在什么時(shí)代背景下發(fā)明產(chǎn)生的?
3、這個(gè)功能有什么用?
4、怎么實(shí)現(xiàn)的?
5、優(yōu)點(diǎn)和缺點(diǎn)是什么?
6、這個(gè)功能能應(yīng)用在工作中?
這是對(duì)自我的提問,我認(rèn)為帶著問題去學(xué)習(xí),是一種更好的學(xué)習(xí)方式,有利于加深理解。好了,接下來進(jìn)入主題。
(一)起源
????????在上篇文章中我們講到 Spring 注解雖然可以代替以往XML的形式,幫助我們自動(dòng)注冊(cè)Bean以及初始化組件,簡(jiǎn)化我們的開發(fā),但還是做不到真正意義上的自動(dòng)裝配,今天我們就來講講 Spring Boot 是如何深度整合 Spring 注解編程模型、@Enable 模塊驅(qū)動(dòng)及條件裝配等 Spring 原生特性來實(shí)現(xiàn)自動(dòng)裝配的。
注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOT
(二)Spring Boot 自動(dòng)裝配實(shí)現(xiàn)
????????我們都知道 Spring Boot 的啟動(dòng)過程非常簡(jiǎn)單,只需要啟動(dòng)一個(gè) main 方法,項(xiàng)目就可以運(yùn)行,就算依賴了諸多外部模塊如:MVC、Redis等,也不需要我們進(jìn)行過多的配置,那它的底層原理是什么呢?接下來,我們就一起去看一看。
我們先來看一段 Spring Boot 的啟動(dòng)類代碼:
@SpringBootApplication
public class LoongSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(LoongSpringBootApplication.class, args);
}
}
我們需要關(guān)注的是 @SpringBootApplication
這個(gè)注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
我們來看一看它的組成部分:
-
@SpringBootConfiguration
:它里面標(biāo)注了@Configuration
注解,上篇文章說過,表明這是個(gè)配置類,功能與@Configuration
無異。 -
@EnableAutoConfiguration
:這個(gè)就是實(shí)現(xiàn)自動(dòng)裝配的核心注解,是用來激活自動(dòng)裝配的,其中默認(rèn)路徑掃描以及組件裝配、排除等都通過它來實(shí)現(xiàn)。 -
@ComponentScan
:上篇文章我們講過這是用來掃描被@Component
標(biāo)注的類 ,只不過這里是用來過濾 Bean 的,指定哪些類不進(jìn)行掃描,而且用的是自定義規(guī)則。 -
Class<?>[] exclude()
:根據(jù)class來排除,排除指定的類加入spring容器,傳入的類型是class類型。且繼承自@EnableAutoConfiguration
中的屬性。 -
String[] excludeName()
:根據(jù)class name來排除,排除特定的類加入spring容器,參數(shù)類型是class的全類名字符串?dāng)?shù)組。同樣繼承自@EnableAutoConfiguration
。 -
String[] scanBasePackages()
:可以指定多個(gè)包名進(jìn)行掃描。繼承自@ComponentScan
。 -
Class<?>[] scanBasePackageClasses()
:可以指定多個(gè)類或接口的class,然后掃描 class 所在包下的所有組件。同樣繼承自@ComponentScan
。
1、@EnableAutoConfiguration 實(shí)現(xiàn)
????????上面我們說到 @EnableAutoConfiguration
是實(shí)現(xiàn)自動(dòng)裝配的核心注解,是用來激活自動(dòng)裝配的,看注解前綴我們應(yīng)該知道是上篇文章中所講的 Spring @Enable 模塊驅(qū)動(dòng)的設(shè)計(jì)模式,所以它必然會(huì)有 @Import
導(dǎo)入的被 @Configuration
標(biāo)注的類或?qū)崿F(xiàn) ImportSelector
或 ImportBeanDefinitionRegistrar
接口的類。接著,我們來看看它的定義:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
可以看到它由兩部分組成:
-
@AutoConfigurationPackage
:這是用來將啟動(dòng)類所在包,以及下面所有子包里面的所有組件掃描到Spring容器中,這里的組件是指被@Component
或其派生注解標(biāo)注的類。這也就是為什么不用標(biāo)注@ComponentScan
的原因。 -
@Import(AutoConfigurationImportSelector.class)
:這里導(dǎo)入的是實(shí)現(xiàn)了ImportSelector
接口的類,組件自動(dòng)裝配的邏輯均在重寫的selectImports
方法中實(shí)現(xiàn)。
接下來我們就來看看這兩者具體是怎么實(shí)現(xiàn)的。
1.1、獲取默認(rèn)包掃描路徑
我們先來看看 Spring Boot
是如何通過 @AutoConfigurationPackage
注解獲取默認(rèn)包掃描路徑的,進(jìn)入它的實(shí)現(xiàn):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
可以看到它是通過 @Import
導(dǎo)入了 AutoConfigurationPackages.Registrar
類,該類實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar
接口,所以按照上篇文章所講的,它是在重寫的方法中直接注冊(cè)相關(guān)組件。繼續(xù)往下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
....
}
private static final class PackageImport {
private final String packageName;
PackageImport(AnnotationMetadata metadata) {
this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}
....
}
這里主要是通過 metadata
元數(shù)據(jù)信息構(gòu)造 PackageImport
類。先獲取啟動(dòng)類的類名,再通過 ClassUtils.getPackageName
獲取啟動(dòng)類所在的包名。我們接著往下看:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
最后就是將這個(gè)包名保存至 BasePackages
類中,然后通過 BeanDefinitionRegistry
將其注冊(cè),進(jìn)行后續(xù)處理,至此該流程結(jié)束。
1.2、獲取自動(dòng)裝配的組件
該部分就是實(shí)現(xiàn)自動(dòng)裝配的入口,從上面得知這里也是通過 @Import
來實(shí)現(xiàn)的,來看看導(dǎo)入的類:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
....
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
....
}
主要關(guān)注重寫的 selectImports
方法,其中 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
是加載自動(dòng)裝配的元信息。而AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)
該方法返回的就是自動(dòng)裝配的組件,我們進(jìn)去看看:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 獲取 @EnableAutoConfigoration 標(biāo)注類的元信息,也就是獲取該注解 exclude 和 excludeName 屬性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 該方法就是獲取自動(dòng)裝配的類名集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重復(fù)的自動(dòng)裝配組件,就是將List轉(zhuǎn)為Set進(jìn)行去重
configurations = removeDuplicates(configurations);
// 這部分就是根據(jù)上面獲取的 exclude 及 excludeName 屬性值,排除指定的類
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 這里是過濾那些依賴不滿足的自動(dòng)裝配 Class
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回的就是經(jīng)過一系列去重、排除、過濾等操作后的自動(dòng)裝配組件
return new AutoConfigurationEntry(configurations, exclusions);
}
該方法中就是先獲取待自動(dòng)裝配組件的類名集合,然后通過一些列的去重、排除、過濾,最終返回自動(dòng)裝配的類名集合。主要關(guān)注 getCandidateConfigurations(annotationMetadata, attributes)
這個(gè)方法,里面是如何獲取自動(dòng)裝配的類名集合:
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;
}
其中getSpringFactoriesLoaderFactoryClass()
返回的是EnableAutoConfiguration.class
。
繼續(xù)往下,執(zhí)行的是 SpringFactoriesLoader#loadFactoryNames
方法:
public final class SpringFactoriesLoader {
...
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 前面可以看到,這里的 factoryClass 是 EnableAutoConfiguration.class
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
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);
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) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
...
}
最終的實(shí)現(xiàn)邏輯都在這里,主要過程如下:
(1)搜索classpath路徑下以及所有外部jar包下的META-INF文件夾中的spring.factories
文件。主要是spring-boot-autoconfigur
包下的
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
...
可以看到其中內(nèi)容,存儲(chǔ)的是key-value格式的數(shù)據(jù),且key是一個(gè)類的全路徑名稱,value是多個(gè)類的全路徑名稱,且以逗號(hào)分割。
(2)將所有的spring.factories
文件轉(zhuǎn)成Properties
格式,將里面key-value格式的數(shù)據(jù)轉(zhuǎn)成Map,該Map的value是一個(gè)List,之后將相同Key的value合并到List中,將該Map作為方法返回值返回。
(3)返回到 loadFactoryNames
方法,通過上面得知factoryClassName
的值為EnableAutoConfiguration
,所以通過 getOrDefault(factoryClassName, Collections.emptyList())
方法,獲取 key 為EnableAutoConfiguration
的類名集合。
ps:
getOrDefault
第一個(gè)入?yún)⑹莐ey的name,如果key不存在,則直接返回第二個(gè)參數(shù)值
至此,流程結(jié)束,最后返回的就是自動(dòng)裝配的組件,可以看到一個(gè)特點(diǎn),這些自動(dòng)裝配的組件都是以 AutoConfiguration
結(jié)尾。但該組件列表只是候選組件,因?yàn)楹竺孢€有去重、排除、過濾等一系列操作,這里就不再詳細(xì)述說。下面我們來看看自動(dòng)裝配的組件內(nèi)部是怎么樣的。
2、自動(dòng)裝配的組件內(nèi)部實(shí)現(xiàn)
就拿比較熟悉的 Web MVC 來看,看看是如何實(shí)現(xiàn) Web MVC 自動(dòng)裝配的。先來代碼組成部分:
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware {
...
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
...
}
...
}
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
...
}
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
...
}
}
...
}
-
注解部分:
-
@Configuration
:這個(gè)大家都比較熟悉,標(biāo)識(shí)該類是一個(gè)配置類 -
@ConditionalXXX
:這是上篇文章所講的 Spring 條件裝配,只不過經(jīng)由 Spring Boot 擴(kuò)展形成了自己的條件化自動(dòng)裝配,且都是@Conditional
的派生注解。-
@ConditionalOnWebApplication
:參數(shù)值是 Type 類型的枚舉,當(dāng)前項(xiàng)目類型是任意、Web、Reactive其中之一則實(shí)例化該 Bean。這里指定如果為 Web 項(xiàng)目才滿足條件。 -
@ConditionalOnClass
:參數(shù)是 Class 數(shù)組,當(dāng)給定的類名在類路徑上存在,則實(shí)例化當(dāng)前Bean。這里當(dāng)Servlet.class
、DispatcherServlet.class
、WebMvcConfigurer.class
存在才滿足條件。 -
@ConditionalOnMissingBean
:參數(shù)是也是 Class 數(shù)組,當(dāng)給定的類沒有實(shí)例化時(shí),則實(shí)例化當(dāng)前Bean。這里指定當(dāng)WebMvcConfigurationSupport
該類沒有實(shí)例化時(shí),才滿足條件。
-
- 裝配順序
-
@AutoConfigureOrder
:參數(shù)是int類型的數(shù)值,數(shù)越小越先初始化。 -
@AutoConfigureAfter
:參數(shù)是 Class 數(shù)組,在指定的配置類初始化后再加載。 -
@AutoConfigureBefore
:參數(shù)同樣是 Class 數(shù)組,在指定的配置類初始化前加載。
-
-
-
代碼部分:
- 這部分就比較直接了,實(shí)例化了和 Web MVC 相關(guān)的Bean,如
HandlerAdapter
、HandlerMapping
、ViewResolver
等。其中,出現(xiàn)了DelegatingWebMvcConfiguration
類,這是上篇文章所講的@EnableWebMvc
所@Import
導(dǎo)入的配置類。
- 這部分就比較直接了,實(shí)例化了和 Web MVC 相關(guān)的Bean,如
可以看到,在Spring Boot
自動(dòng)裝配的類中,經(jīng)過了一系列的 @Conditional
條件判斷,然后實(shí)例化某個(gè)模塊需要的Bean,且無需我們配置任何東西,當(dāng)然,這都是默認(rèn)實(shí)現(xiàn),當(dāng)這些不滿足我們的要求時(shí),我們還得手動(dòng)操作。
(三)總結(jié)
????????關(guān)于Spring boot自動(dòng)裝配的內(nèi)容就告一段落,不難看出Spring Boot自動(dòng)裝配所依賴的注解驅(qū)動(dòng)、@Enable
模塊驅(qū)動(dòng)、條件裝配等特性均來自 Spring Framework。而自動(dòng)裝配的配置類均來源于spring.factories
文件中。核心則是基于“約定大于配置”理念,通俗的說,就是Spring boot為我們提供了一套默認(rèn)的配置,只有當(dāng)默認(rèn)的配置不滿足我們的需求時(shí),我們?cè)偃バ薷哪J(rèn)配置。當(dāng)然它也存在缺點(diǎn)就是組件的高度集成,使用的時(shí)候很難知道底層實(shí)現(xiàn),加深了理解難度。
以上就是本章的內(nèi)容,如過文章中有錯(cuò)誤或者需要補(bǔ)充的請(qǐng)及時(shí)提出,本人感激不盡。
參考:
《Spring Boot 編程思想》