spring boot 的 auto-configuration 功能會根據你的應用程序所依賴的 pom 來進行自動配置。 例如,咱們在 pom 中添加
spring-boot-starter-web
的依賴,spring 就會幫咱們自動完成 spring mvc 相關的配置而不須要咱們手動來進行。咱們只須要將@EnableAutoConfiguration
或者@SpringBootApplication
註解標註在@Configuration
配置類上面便可啓用自動裝配。web
那麼 auto-configuration 是如何工做的呢?帶着問題,咱們經過閱讀相關的源代碼來一探究竟。spring
既然開啓 auto-configuration 須要經過 @EnableAutoConfiguration
或 @SpringBootApplication
來驅動,那麼咱們就從這兩個註解着手。api
注:本文基於 spring boot 版本 1.5.12.RELEASE。數組
@SpringBootApplication
websocket
源碼以下:mvc
@Target(ElementType.TYPE)
app
@Retention(RetentionPolicy.RUNTIME)
socket
@Documented
ide
@Inherited
spring-boot
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 省略
}
@SpringBootApplication
註解自己也標註了 @EnableAutoConfiguration
註解,因此自動裝配最終仍是經過 @EnableAutoConfiguration
來啓用。
@SpringBootApplication
還標註了 @ComponentScan
和 @SpringBootConfiguration
, @SpringBootConfiguration
上又標註 @Configuration
註解。
由於 @SpringBootApplication
將上述多個註解集成於一身,因此咱們只要在類上標註 @SpringBootApplication
就等價於同時添加了 @EnableAutoConfiguration
、 @ComponentScan
和 @Configuration
。
注:類上標註的註解(direct annotation)和註解上的註解(meta-annotation)之因此都能生效這和 spring 自己對註解的處理有關。
@SpringBootApplication
比較簡單,看來再來看看 @EnableAutoConfiguration
。
@EnableAutoConfiguration
源碼以下:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@EnableAutoConfiguration
上標註了 @Import
,引入了 EnableAutoConfigurationImportSelector
。
EnableAutoConfigurationImportSelector
源碼以下:
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
EnableAutoConfigurationImportSelector
源碼並沒能給咱們提供太多參考信息,只重寫了 isEnabled
方法,咱們來看看他的基類 AutoConfigurationImportSelector
,源碼以下:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
// 省略
}
咱們看到 AutoConfigurationImportSelector
實現了 DeferredImportSelector
接口(從 ImportSelector
派生)。 ImportSelector
源碼以下:
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
selectImports
方法中定義了參數importingClassMetadata
,類型是AnnotationMetadata
,這是啥?咱們把
importingClassMetadata
這個詞組分解一下,importing + class metadata:importing:動名詞,強調的是幹了 import 這件事的發起者(這裏指配置類)
class metadata:類的元信息,什麼元信息,註解元信息,即
AnnotationMetadata
。那麼
selectImports
方法的邏輯,咱們能夠這麼描述:基於發起 import 操做的配置類(@Configuration class)的元信息進行運算並返回計算結果(class 名稱數組)。
那麼 spring 對返回結果中的 class 有沒有什麼特別要求呢?
實際上你能夠返回任意的 class 名稱,而不至於使程序出錯,固然,你確定不會返回沒有任何意義的 class 名稱。筆者總結的返回結果分爲下面兩類:
@Configuration
配置類
ImportSelector
(或DeferredImportSelector
)實現類
ImportSelector
與DeferredImportSelector
的區別?
DeferredImportSelector
是ImportSelector
的變體,兩者的觸發前後順序不一樣,DeferredImportSelector
在全部的@Configuration
bean 都被處理了以後再進行處理,這與它的名稱 deferred (推遲)很是貼合。題外話:經過以上內容,咱們能夠看出 spring 在命名上很是講究,代碼閱讀起來比較符合人的邏輯思惟。
selectImports
方法的實現由ConfigurationClassParser
類的 parse 方法觸發,ConfigurationClassParser
的 parse 方法會被ConfigurationClassPostProcessor
類的 postProcessBeanDefinitionRegistry 方法調用,而觸發這一切的最上層是 spring application context 的refresh()
方法。
信息量有點大,咱們經過下圖來作簡單說明:
Application 啓動
⥥ // refresh spring application context
AbstractApplicationContext.refresh
⥥ // 執行 BeanFactoryPostProcessor 鉤子回調
AbstractApplicationContext.invokeBeanFactoryPostProcessors
⥥ // 委託給 PostProcessorRegistrationDelegate 來執行鉤子回調
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
⥥ // ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的實現類,被觸發
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
⥥ // ConfigurationClassPostProcessor 委託 ConfigurationClassParser 來對配置類進行解析
ConfigurationClassParser.parse
⥥ // ConfigurationClassParser 處理 DeferredImportSelector
ConfigurationClassParser.processDeferredImportSelectors
⥥ // 執行 selectImports
DeferredImportSelector.selectImports
關於 ConfigurationClassParser
類對 @Configuration
配置類的處理,本文並不打算做過多講解,筆者會放在後續文章中來和你們進行探討。
好了,有了對 ImportSelector
的相關了解後,咱們來看看 AutoConfigurationImportSelector
的 selectImports
方法的實現,源碼以下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 檢查是否啓用 auto-configuration,默認爲啓用
// 由子類 EnableAutoConfigurationImportSelector 重寫
if (!isEnabled(annotationMetadata)) {
// 若是不開啓 auto-configuration,返回 emtpy array
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 經過 SpringFactoriesLoader 找到全部的侯選配置類(auto-configuration 類)
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去除重複的配置類
configurations = removeDuplicates(configurations);
// 根據 auto-configuration 的前後配置順序的要求進行排序
// 可經過 @AutoConfigureBefore & @AutoConfigureAfter 來指定
configurations = sort(configurations, autoConfigurationMetadata);
// 須要被剔除的 auto-configuration 類
// 可在 properties 配置文件中經過 spring.autoconfigure.exclude 來指定
// 也可經過 @EnableAutoConfiguration 註解的 exclude() & excludeName() 屬性來指定
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 從侯選配置類中剔除須要被排除在外的(auto-configuration 類)
configurations.removeAll(exclusions);
// 經過 AutoConfigurationImportFilter 來過濾侯選配置類,再次進行剔除
// 經過 SpringFactoriesLoader 獲取所的 AutoConfigurationImportFilter 實現
configurations = filter(configurations, autoConfigurationMetadata);
// 發佈 AutoConfigurationImportEvent 事件,通知 AutoConfigurationImportListener,
// 觸發 onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) 方法
// 經過 SpringFactoriesLoader 獲取所的 AutoConfigurationImportListener 實現
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回須要處理的 auto-configuration 類(名稱)
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return true;
}
相信你們看了筆者所添加的相關注釋後對 AutoConfigurationImportSelector
的邏輯已經有了一個大體的瞭解。
AutoConfigurationImportSelector
的selectImports
方法的主要邏輯就是經過SpringFactoriesLoader
找到全部的 auto-configuration 侯選類,而後在此基礎上進行去重、排序和剔除操做,最終獲得須要進行 auto-configuration 的全部類的名稱。拿到了所的 auto-configuration 類,spring boot 就能夠加載這些 class,因爲這些類自己標註了
@Configuration
,而後就能夠被ConfigurationClassParser
類來進行解析了,最終@Bean
工廠方法就會被調用,完成 bean 的加載。
在筆者的註釋中,不止一次提到了 SpringFactoriesLoader
,這個類究竟有何神奇之處?
一樣的,爲了搞清楚這件事,咱們還得看 spring 源碼,部分源碼以下:
public abstract class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 經過 ClassLoader 加載全部的全部 META-INF/spring.factories 文件,解析成 Properties 對象,
// 根據 key (指定的 class 名稱) 來獲取全部的配置項 (類名稱)
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
// 省略
}
}
}
原來 spring 使用了一種相似
ServiceLoader
(jdk 1.6 中增長)的處理方式。經過這種約定,咱們只須要將要進行處理的目標類名稱配置在相對固定的配置文件中,spring 按照統一的方式來讀取便可。對咱們的應用程序而言並不須要知道具體的實現類是哪些,也不須要知道這些類存在於哪些 jar 中,均可以被輕鬆獲取。相對固定指的是:
相對於 classpath, META-INF/spring.factories 文件的位置固定和名稱固定。
同一類別的配置,不一樣配置文件中 key 名稱固定不變。
注:若是你對
ServiceLoader
感到陌生,請查看 jdk api 瞭解相關內容。
既然瞭解了 spring 的 spring.factories
的套路,那麼咱們就來找找看都有哪些 auto-configuration 類,
咱們的 key 是 @EnableAutoConfiguration
註解的 class 名稱,即 org.springframework.boot.autoconfigure.EnableAutoConfiguration
,展開 spring-boot-autoconfigure-1.5.12.RELEASE.jar 文件,找到 spring.factories
文件,部分配置以下(「……」 表示筆者省略 ):
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
# ……
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
# ……
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
# ……
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
# ……
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
上述這些 auto-configuration 侯選類名稱都會被
AutoConfigurationImportSelector
經過SpringFactoriesLoader
所獲取,而後AutoConfigurationImportSelector
就能夠進行後續的去重、排序和剔除過濾操做了。
好了,本文至此,你是否已經對 spring boot 的 auto-configuration 工做機制有所瞭解了呢?
本文,咱們主要介紹了 spring boot auto-configuration 的工做機制,提到了幾個重要的概念:
@EnableAutoConfiguration
ImportSelector
SpringFactoriesLoader
經過
@EnableAutoConfiguration
註解驅動引入AutoConfigurationImportSelector
,該類經過SpringFactoriesLoader
加載全部的META-INF/spring.factories
文件,獲取到全部的 auto-configuration 候選類,而後進行去重、排序和剔除過濾等操做獲得待處理的 auto-configuration 類。