目錄java
最近在學習Spring Boot相關的課程,過程當中以筆記的形式記錄下來,方便之後回憶,同時也在這裏和你們探討探討,文章中有漏的或者有補充的、錯誤的都但願你們可以及時提出來,本人在此先謝謝了!react
開始以前呢,但願你們帶着幾個問題去學習:
一、Spring Boot 自動裝配是什麼?
二、這個功能在什麼時代背景下發明產生的?
三、這個功能有什麼用?
四、怎麼實現的?
五、優勢和缺點是什麼?
六、這個功能能應用在工做中?
這是對自個人提問,我認爲帶着問題去學習,是一種更好的學習方式,有利於加深理解。好了,接下來進入主題。web
在上篇文章中咱們講到 Spring 註解雖然能夠代替以往XML的形式,幫助咱們自動註冊Bean以及初始化組件,簡化咱們的開發,但仍是作不到真正意義上的自動裝配,今天咱們就來說講 Spring Boot 是如何深度整合 Spring 註解編程模型、@Enable 模塊驅動及條件裝配等 Spring 原生特性來實現自動裝配的。redis
注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOTspring
咱們都知道 Spring Boot 的啓動過程很是簡單,只須要啓動一個 main 方法,項目就能夠運行,就算依賴了諸多外部模塊如:MVC、Redis等,也不須要咱們進行過多的配置,那它的底層原理是什麼呢?接下來,咱們就一塊兒去看一看。編程
咱們先來看一段 Spring Boot 的啓動類代碼:設計模式
@SpringBootApplication public class LoongSpringBootApplication { public static void main(String[] args) { SpringApplication.run(LoongSpringBootApplication.class, args); } }
咱們須要關注的是 @SpringBootApplication
這個註解:數組
@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 {}; }
咱們來看一看它的組成部分:app
@SpringBootConfiguration
:它裏面標註了 @Configuration
註解,上篇文章說過,代表這是個配置類,功能與 @Configuration
無異。@EnableAutoConfiguration
:這個就是實現自動裝配的核心註解,是用來激活自動裝配的,其中默認路徑掃描以及組件裝配、排除等都經過它來實現。@ComponentScan
:上篇文章咱們講過這是用來掃描被 @Component
標註的類 ,只不過這裏是用來過濾 Bean 的,指定哪些類不進行掃描,並且用的是自定義規則。Class<?>[] exclude()
:根據class來排除,排除指定的類加入spring容器,傳入的類型是class類型。且繼承自 @EnableAutoConfiguration
中的屬性。String[] excludeName()
:根據class name來排除,排除特定的類加入spring容器,參數類型是class的全類名字符串數組。一樣繼承自 @EnableAutoConfiguration
。String[] scanBasePackages()
:能夠指定多個包名進行掃描。繼承自 @ComponentScan
。Class<?>[] scanBasePackageClasses()
:能夠指定多個類或接口的class,而後掃描 class 所在包下的全部組件。一樣繼承自 @ComponentScan
。 上面咱們說到 @EnableAutoConfiguration
是實現自動裝配的核心註解,是用來激活自動裝配的,看註解前綴咱們應該知道是上篇文章中所講的 Spring @Enable 模塊驅動的設計模式,因此它必然會有 @Import
導入的被 @Configuration
標註的類或實現 ImportSelector
或 ImportBeanDefinitionRegistrar
接口的類。接着,咱們來看看它的定義:ide
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
能夠看到它由兩部分組成:
@AutoConfigurationPackage
:這是用來將啓動類所在包,以及下面全部子包裏面的全部組件掃描到Spring容器中,這裏的組件是指被 @Component
或其派生註解標註的類。這也就是爲何不用標註@ComponentScan
的緣由。@Import(AutoConfigurationImportSelector.class)
:這裏導入的是實現了 ImportSelector
接口的類,組件自動裝配的邏輯均在重寫的 selectImports
方法中實現。接下來咱們就來看看這二者具體是怎麼實現的。
咱們先來看看 Spring Boot
是如何經過 @AutoConfigurationPackage
註解獲取默認包掃描路徑的,進入它的實現:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
能夠看到它是經過 @Import
導入了 AutoConfigurationPackages.Registrar
類,該類實現了 ImportBeanDefinitionRegistrar
接口,因此按照上篇文章所講的,它是在重寫的方法中直接註冊相關組件。繼續往下:
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
元數據信息構造 PackageImport
類。先獲取啓動類的類名,再經過 ClassUtils.getPackageName
獲取啓動類所在的包名。咱們接着往下看:
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); } }
最後就是將這個包名保存至 BasePackages
類中,而後經過 BeanDefinitionRegistry
將其註冊,進行後續處理,至此該流程結束。
該部分就是實現自動裝配的入口,從上面得知這裏也是經過 @Import
來實現的,來看看導入的類:
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()); } .... }
主要關注重寫的 selectImports
方法,其中 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
是加載自動裝配的元信息。而AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)
該方法返回的就是自動裝配的組件,咱們進去看看:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 獲取 @EnableAutoConfigoration 標註類的元信息,也就是獲取該註解 exclude 和 excludeName 屬性值 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 該方法就是獲取自動裝配的類名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去除重複的自動裝配組件,就是將List轉爲Set進行去重 configurations = removeDuplicates(configurations); // 這部分就是根據上面獲取的 exclude 及 excludeName 屬性值,排除指定的類 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); // 這裏是過濾那些依賴不知足的自動裝配 Class configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); // 返回的就是通過一系列去重、排除、過濾等操做後的自動裝配組件 return new AutoConfigurationEntry(configurations, exclusions); }
該方法中就是先獲取待自動裝配組件的類名集合,而後經過一些列的去重、排除、過濾,最終返回自動裝配的類名集合。主要關注 getCandidateConfigurations(annotationMetadata, attributes)
這個方法,裏面是如何獲取自動裝配的類名集合:
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
。
繼續往下,執行的是 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); } } ... }
最終的實現邏輯都在這裏,主要過程以下:
(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.jdbc.JdbcTemplateAutoConfiguration,\ 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 ...
能夠看到其中內容,存儲的是key-value格式的數據,且key是一個類的全路徑名稱,value是多個類的全路徑名稱,且以逗號分割。
(2)將全部的spring.factories
文件轉成Properties
格式,將裏面key-value格式的數據轉成Map,該Map的value是一個List,以後將相同Key的value合併到List中,將該Map做爲方法返回值返回。
(3)返回到 loadFactoryNames
方法,經過上面得知factoryClassName
的值爲EnableAutoConfiguration
,因此經過 getOrDefault(factoryClassName, Collections.emptyList())
方法,獲取 key 爲EnableAutoConfiguration
的類名集合。
ps:
getOrDefault
第一個入參是key的name,若是key不存在,則直接返回第二個參數值
至此,流程結束,最後返回的就是自動裝配的組件,其中有咱們比較熟悉的Redis、JDBC、SpringMVC等,能夠看到一個特色,這些自動裝配的組件都是以 AutoConfiguration
結尾。但該組件列表只是候選組件,由於後面還有去重、排除、過濾等一系列操做,這裏就再也不詳細述說。下面咱們來看看自動裝配的組件內部是怎麼樣的。
就拿比較熟悉的 Web MVC 來看,看看是如何實現 Web MVC 自動裝配的。先來代碼組成部分:
@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
:這個你們都比較熟悉,標識該類是一個配置類@ConditionalXXX
:這是上篇文章所講的 Spring 條件裝配,只不過經由 Spring Boot 擴展造成了本身的條件化自動裝配,且都是@Conditional
的派生註解。
@ConditionalOnWebApplication
:參數值是 Type 類型的枚舉,當前項目類型是任意、Web、Reactive其中之一則實例化該 Bean。這裏指定若是爲 Web 項目才知足條件。@ConditionalOnClass
:參數是 Class 數組,當給定的類名在類路徑上存在,則實例化當前Bean。這裏當Servlet.class
、 DispatcherServlet.class
、 WebMvcConfigurer.class
存在才知足條件。@ConditionalOnMissingBean
:參數是也是 Class 數組,當給定的類沒有實例化時,則實例化當前Bean。這裏指定當 WebMvcConfigurationSupport
該類沒有實例化時,才知足條件。@AutoConfigureOrder
:參數是int類型的數值,數越小越先初始化。@AutoConfigureAfter
:參數是 Class 數組,在指定的配置類初始化後再加載。@AutoConfigureBefore
:參數一樣是 Class 數組,在指定的配置類初始化前加載。HandlerAdapter
、HandlerMapping
、ViewResolver
等。其中,出現了 DelegatingWebMvcConfiguration
類,這是上篇文章所講的 @EnableWebMvc
所 @Import
導入的配置類。能夠看到,在Spring Boot
自動裝配的類中,通過了一系列的 @Conditional
條件判斷,而後實例化某個模塊須要的Bean,且無需咱們配置任何東西,固然,這都是默認實現,當這些不知足咱們的要求時,咱們還得手動操做。
關於Spring boot自動裝配的內容就告一段落,不難看出Spring Boot自動裝配所依賴的註解驅動、@Enable
模塊驅動、條件裝配等特性均來自 Spring Framework。而自動裝配的配置類均來源於spring.factories
文件中。核心則是基於「約定大於配置」理念,通俗的說,就是Spring boot爲咱們提供了一套默認的配置,只有當默認的配置不知足咱們的需求時,咱們再去修改默認配置。固然它也存在缺點就是組件的高度集成,使用的時候很難知道底層實現,加深了理解難度。
以上就是本章的內容,如過文章中有錯誤或者須要補充的請及時提出,本人感激涕零。
參考:
《Spring Boot 編程思想》