SpringBoot自動裝配原理
SpringBoot如果問到的話就說說
- 自動裝配(最主要的)
- run方法(次要)
pom.xml
pom.xml主要有四個部分:
項目元數據信息:創建時候輸入的Project Metadata部分,也就是Maven項目的基本元素,包括:groupId、artifactId、version、name、description等
parent:繼承spring-boot-starter-parent的依賴管理,控制版本與打包等內容
dependencies:項目具體依賴,這里包含了spring-boot-starter-web用於實現HTTP接口(該依賴中包含了Spring MVC),官網對它的描述是:使用Spring MVC構建Web(包括RESTful)應用程序的入門者,使用Tomcat作為默認嵌入式容器。;spring-boot-starter-test用於編寫單元測試的依賴包。更多功能模塊的使用我們將在后面逐步展開。
build:構建配置部分。默認使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot應用打包成JAR來直接運行。
- spring-boot-dependencies: 核心工程在父工程中
也就是說我們的項目主要是依賴一個父項目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
進入父項目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
這里才是真正管理SpringBoot應用里面所有依賴版本的地方,SpringBoot的版本控制中心;
以后我們導入依賴默認是不需要寫版本;但是如果導入的包沒有在依賴中管理着就需要手動配置版本了;
- 我們在寫或者引入一些springboot依賴的時候,不需要指定版本,因為在父工程中有了那些版本倉庫
啟動器
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
- 啟動器實際上就是SpringBoot的啟動場景。
- 比如spring-boot-starter-web, 就會幫助項目自動導入Web環境所有的依賴。
- SpringBoot會將所有的功能場景都變成一個個的啟動器。
- SpringBoot將所有的功能場景都抽取出來,做成一個個的starter (啟動器),只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 , 我們要用什么功能就導入什么樣的場景啟動器即可 。官網Starters
主程序
// @SpringBootApplication 標注下面的這個類是一個SpringBoot的應用
@SpringBootApplication
public class HalftownApplication {
public static void main(String[] args) {
// 將SpringBoot 應用啟動
SpringApplication.run(HalftownApplication.class, args);
}
}
注解
@SpringBootConfiguration : SpringBoot的配置類,標注在某個類上, 表示這是一個SpringBoot的配置類
@Configuration: Spring配置類,配置類上來標注這個注解,說明這是一個配置類 ,配置類---即----配置文件;
@Componet: 說明這是一個Spring的組件,啟動類本身也是Spring中的一個組件而已,負責啟動應用
@EnableAutoConfiguration:開啟自動配置功能,以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置,@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效
@AutoConfigurationPackage: 自動配置包
@Import({Registrar.class}): 自動配置包注冊器
@Import({AutoConfigurationImportSelector.class}): 自動配置導入選擇器
(@import :Spring底層注解@import , 給容器中導入一個組件)
Registrar.class 將主配置類 【即@SpringBootApplication標注的類】的所在包及包下面所有子包里面的所有組件掃描到Spring容器 ;
@ComponentScan:這個注解在Spring中很重要 , 它對應XML配置中的元素。@ComponentScan的功能就是自動掃描並加載符合條件的組件或者bean , 將這個bean定義加載到IOC容器中 ;
最終所有的資源都會加載到配置類中
Properties properties=PropertiesLoaderUtils. LoadProperties(resource);
1、@SpringBootApplication
意義:SpringBoot應用標注在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該運行這個類的main方法來啟動SpringBoot應用;
進入這個注解:可以看到上面還有很多其他注解!
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
//.....
}
2、@ComponentScan
這個注解在Spring中很重要 , 它對應XML配置中的元素。@ComponentScan的功能就是自動掃描並加載符合條件的組件或者bean , 將這個bean定義加載到IOC容器中 ;
3、@SpringBootConfiguration
意義:SpringBoot的配置類 ;標注在某個類上 , 表示這是一個SpringBoot的配置類;
我們繼續進去這個注解查看
@Configuration //點進去得到下面的 @Component
public @interface SpringBootConfiguration {
}
@Component
public @interface Configuration {
}
@Configuration:配置類上來標注這個注解,說明這是一個配置類 ,配置類---即----配置文件;
我們繼續點進去,發現配置類也是容器中的一個組件。@Component 。這就說明,啟動類本身也是Spring中的一個組件而已,負責啟動應用!
4、@EnableAutoConfiguration
我們回到 SpringBootApplication 注解中繼續看。
@EnableAutoConfiguration :開啟自動配置功能
以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ; @EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;
我們點擊去查看。發現注解@AutoConfigurationPackage : 自動配置包
@AutoConfigurationPackage //自動配置包
@Import({AutoConfigurationImportSelector.class}) //導入哪些組件的選擇器
public @interface EnableAutoConfiguration {
}
再點進去看到一個 @Import({Registrar.class})
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
@import :Spring底層注解@import , 給容器中導入一個組件
Registrar.class 將主配置類 【即@SpringBootApplication標注的類】的所在包及包下面所有子包里面的所有組件掃描到Spring容器 ;
退到上一步,繼續看:@Import({AutoConfigurationImportSelector.class}) :給容器導入組件 ;
AutoConfigurationImportSelector : 自動配置導入選擇器,那么它會導入哪些組件的選擇器呢? ;
我們點擊去這個類看源碼:
這個類中有一個這樣的方法
// 獲得候選的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//這里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我們最開始看的啟動自動導入配置文件的注解類;EnableAutoConfiguration
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
這個方法又調用了 SpringFactoriesLoader 類的靜態方法!我們進入SpringFactoriesLoader類loadFactoryNames() 方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//這里它又調用了 loadSpringFactories 方法
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
我們繼續點擊查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//獲得classLoader , 我們返回可以看到這里得到的就是EnableAutoConfiguration標注的類本身
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//去獲取一個資源 "META-INF/spring.factories"
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//將讀取到的資源遍歷,封裝成為一個Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
我們根據源頭打開spring.factories的配置文件 , 看到了很多自動配置的文件;這就是自動配置根源所在!
WebMvcAutoConfiguration
我們在上面的自動配置類隨便找一個打開看看,比如 : WebMvcAutoConfiguration
可以看到這些一個個的都是JavaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看着熟悉一下!
所以,
META-INF/spring.factories: 自動配置的的核心文件
springboot所有自動配置都是在啟動的時候掃描並加載:spring.factories所有的自動配置類都在這里面,但是不一定生效,要判斷條件是否成立,只要導入了對應的start,就有對應的啟動器了,有了啟動器,我們自動裝配就會生效,然后就配置成功!
所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射實例化為對應標注了 @Configuration的JavaConfig形式的IOC容器配置類 , 然后將這些都匯總成為一個實例並加載到IOC容器中。
下圖請放大認真食用
結論:
- SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
- 將這些值作為自動配置類導入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
- 以前我們需要自己配置的東西 , 自動配置類都幫我們解決了
- 整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
- 它將所有需要導入的組件以全類名的方式返回 , 這些組件就會被添加到容器中 ;
- 它會給容器中導入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中導入這個場景需要的所有組件 , 並配置好這些組件 ;
- 有了自動配置類 , 免去了我們手動編寫配置注入功能組件等的工作;
Run
我最初以為就是運行了一個main方法,沒想到卻開啟了一個服務;
@SpringBootApplication
public class SpringbootDemo02Application {
public static void main(String[] args) {
//該方法返回一個ConfigurableApplicationContext對象
//參數一:應用入口的類 參數類:命令行參數
SpringApplication.run(SpringbootDemo02Application.class, args);
}
}
SpringApplication.run分析
分析該方法主要分兩部分:
- 一是SpringApplication的實例化,
- 二是run方法的執行;
SpringApplication
這個類主要做了以下四件事情
- 推斷應用的類型是普通的項目還是Web項目
- 查找並加載所有可用初始化器 , 設置到initializers屬性中
- 找出所有的應用程序監聽器,設置到listeners屬性中
- 推斷並設置main方法的定義類,找到運行的主類
查看構造器
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
run方法

部分內容來自於秦疆のJava世界