SpringBoot自動配置的原理詳解
一、構(gòu)建系統(tǒng)
1.1 依賴管理
Spring Boot 的每個版本都提供了它支持的依賴項的精選列表。實際上,您不需要在構(gòu)建配置中為任何這些依賴項提供版本,因為 Spring Boot 會為您管理。當您升級 Spring Boot 本身時,這些依賴項也會以一致的方式升級。
如果需要,您仍然可以指定版本并覆蓋 Spring Boot 的建議。
依賴管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
他的父項目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
幾乎聲明了所有開發(fā)中常用的依賴的版本號,自動版本仲裁機制1.2 Starters 啟動器
Starters 是一組方便的依賴描述符,您可以將其包含在您的應(yīng)用程序中。您可以獲得所需的所有 Spring 和相關(guān)技術(shù)的一站式商店,而無需搜索示例代碼和復(fù)制粘貼加載的依賴描述符。例如,如果您想開始使用 Spring 和 JPA 進行數(shù)據(jù)庫訪問,請將spring-boot-starter-data-jpa依賴項包含在您的項目中。
啟動器包含許多依賴項,您需要這些依賴項使項目快速啟動并運行,并具有一致的、受支持的托管傳遞依賴項集。
1、見到很多 spring-boot-starter-* : *就某種場景
2、只要引入starter,這個場景的所有常規(guī)需要的依賴我們都自動引入
3、SpringBoot所有支持的場景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、見到的 *-spring-boot-starter: 第三方為我們提供的簡化開發(fā)的場景啟動器。
5、所有場景啟動器最底層的依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
1、引入依賴默認都可以不寫版本
2、引入非版本仲裁的jar,要寫版本號
查看spring-boot-dependencies里面規(guī)定當前依賴的版本用的key. 在當前項目里面重寫配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>以下應(yīng)用程序啟動器由該org.springframework.boot組下的 Spring Boot 提供:


1.3 自動配置
1、 ctrl+點擊pom.xml文件中的spring-boot-starter-web可以打開starter-web的配置信息

在這個文件中,我們可以看到又自動配置了
1、自動配好SpringMVC
○ 自動配好SpringMVC
○ 自動配好SpringMVC常用組件(功能)
2、自動配好Web常見功能,如:字符編碼問題
○ SpringBoot幫我們配置好了所有web開發(fā)的常見場景
3、默認的包結(jié)構(gòu)
○ 主程序所在包及其下面的所有子包里面的組件都會被默認掃描進來,無需以前的包掃描配置
○ 想要改變掃描路徑,在服務(wù)啟動類上面加上注解@ComponentScan
如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
4、按需加載所有自動配置項
○ 非常多的starter
○ 引入了哪些場景這個場景的自動配置才會開啟
○ SpringBoot所有的自動配置功能都在 spring-boot-autoconfigure 包里面

二、容器功能
2.1 組件添加
2.1.1 @Configuration
Full模式與Lite模式
- 類組件之間無依賴關(guān)系用Lite模式加速容器啟動過程,減少判斷
- 配置類組件之間有依賴關(guān)系,方法會被調(diào)用得到之前單實例組件,用Full模式
1、配置類里面使用@Bean標注在方法上給容器注冊組件,默認也是單實例的
2、配置類本身也是組件
3、proxyBeanMethods:代理bean的方法
Full(proxyBeanMethods = true)、【保證每個@Bean方法被調(diào)用多少次返回的組件都是單實例的】
Lite(proxyBeanMethods = false)【每個@Bean方法被調(diào)用多少次返回的組件都是新創(chuàng)建的】
組件依賴必須使用Full模式默認。其他默認是否Lite模式
@Configuration(proxyBeanMethods = false) //告訴SpringBoot這是一個配置類 == 配置文件
public class MyConfig {
/**
* Full:外部無論對配置類中的這個組件注冊方法調(diào)用多少次獲取的都是之前注冊容器中的單實例對象
* @return
*/
@Bean //給容器中添加組件。以方法名作為組件的id。返回類型就是組件類型。返回的值,就是組件在容器中的實例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user組件依賴了Pet組件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
################################@Configuration測試代碼如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {
public static void main(String[] args) {
//1、返回我們IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2、查看容器里面的組件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//3、從容器中獲取組件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("組件:"+(tom01 == tom02));
//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
//如果@Configuration(proxyBeanMethods = true)代理對象調(diào)用方法。SpringBoot總會檢查這個組件是否在容器中有。
//保持組件單實例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用戶的寵物:"+(user01.getPet() == tom));
}
}2.1.2 @Import
@Import({User.class, DBHelper.class}) 給容器中自動創(chuàng)建出這兩個類型的組件、默認組件的名字就是全類名
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告訴SpringBoot這是一個配置類 == 配置文件
public class MyConfig {
}2.1.3 @Conditional (條件裝配)
條件裝配:滿足Conditional指定的條件,則進行組件注入
- @ConditionalOnProperty 注解
- @ConditionalOnMissingBean注解
@ConditionalOnMissingBean,它是修飾bean的一個注解,主要實現(xiàn)的是,當你的bean被注冊之后,如果而注冊相同類型的bean,就不會成功,它會保證你的bean只有一個,即你的實例只有一個,當你注冊多個相同的bean時,會出現(xiàn)異常,以此來告訴開發(fā)人員。
@Component
public class AutoConfig {
@Bean
public AConfig aConfig() {
return new AConfig("lind");
}
@Bean
@ConditionalOnMissingBean(AMapper.class)
public AMapper aMapper1(AConfig aConfig) {
return new AMapperImpl1(aConfig);
}
@Bean
public AMapper aMapper2(AConfig aConfig) {
return new AMapperImpl2(aConfig);
}
}因為在aMapper1上面標識了AMapper類型的bean只能有一個實現(xiàn) @ConditionalOnMissingBean(AMapper.class),所以在進行aMapper2注冊時,系統(tǒng)會出現(xiàn)上面圖上的異常,這是正常的。 當我們把 @ConditionalOnMissingBean(AMapper.class) 去掉之后,你的bean可以注冊多次,這時需要用的@Primary來確定你要哪個實現(xiàn);一般來說,對于自定義的配置類,我們應(yīng)該加上@ConditionalOnMissingBean注解,以避免多個配置同時注入的風險。
@Primary標識哪個是默認的bean
@Bean
public AMapper aMapper1(AConfig aConfig) {
return new AMapperImpl1(aConfig);
}
@Bean
@Primary
public AMapper aMapper2(AConfig aConfig) {
return new AMapperImpl2(aConfig);
}@ConditionalOnProperty 通過其三個屬性prefix,name以及havingValue來實現(xiàn)的,其中prefix表示配置文件里節(jié)點前綴,name用來從application.properties中讀取某個屬性值,havingValue表示目標值。
如果該值為空,則返回false; 如果值不為空,則將該值與havingValue指定的值進行比較,如果一樣則返回true;否則返回false。 返回值為false,則該configuration不生效;為true則生效。 下面代碼演示為配置文件lind.redis.enable為true時才會注冊RedisFactory這個bean
@Configuration
@ConditionalOnProperty(prefix="lind.redis",name = "enable", havingValue = "true")
public class RedisConfig {
@Bean
public RedisMap redisMap(){
return new RedisMapImpl();
}
}- @ConditionalOnBean // 當給定的在bean存在時,則實例化當前Bean
- @ConditionalOnMissingBean // 當給定的在bean不存在時,則實例化當前Bean
- @ConditionalOnClass // 當給定的類名在類路徑上存在,則實例化當前Bean
- @ConditionalOnMissingClass // 當給定的類名在類路徑上不存在,則實例化當前Bean
2.2 原生配置文件引入
2.2.1 @ImportResource
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="haha" class="stu01.com.bean.User">
<property name="name" value="zhangsan"></property>
<property name="id" value="18"></property>
</bean>
<bean id="hehe" class="stu01.com.bean.User">
<property name="name" value="lisi"></property>
<property name="id" value="20"></property>
</bean>
</beans>測試
package stu01.com.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import stu01.com.bean.User;
@Configuration
@ImportResource("classpath:beans.xml")
public class Myconfig {
@Autowired
ApplicationContext applicationContext;
@Bean
public User getUser(){
applicationContext.getBean("myconfig");
boolean haha = applicationContext.containsBean("haha");
boolean hehe = applicationContext.containsBean("hehe");
System.out.println("haha:"+haha);//true
System.out.println("hehe:"+hehe);//true
User user=(User) applicationContext.getBean("haha");
System.out.println(user.toString());
return user;
}
}2.3 配置綁定
如何使用Java讀取到properties文件中的內(nèi)容,并且把它封裝到JavaBean中,以供隨時使用;
package stu01.com.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import stu01.com.bean.User;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
@Configuration
@ImportResource("classpath:beans.xml")
public class Myconfig {
@Autowired
ApplicationContext applicationContext;
@Bean
public User getUser() throws IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("E:\\IdealWork\\SpringBootStu\\Stu01\\src\\main\\resources\\a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
User user = new User();
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封裝到JavaBean。
if(strKey.equals("name")){
user.setName(strValue);
}
if(strKey.equals("id")){
user.setId(strValue);
}
}
System.out.println(user.toString());
return user;
}
}結(jié)果:

2.3.1 使用注解獲取配置文件中的配置信息
@ConfigurationProperties(prefix = “mycar”) 在需要被注入值的類上添加注解,prefix的值,與配置文件中的前綴一一對應(yīng)。
package stu01.com.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "user-info")
public class User {
String name;
String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
'}';
}
}application.properties配置文件
userInfo.name="lisi" userInfo.id=88888
@Configuration
@ConfigurationProperties(prefix = “user-info”)
@EnableConfigurationProperties(User.class)
在配置類上添加以上注解測試
package stu01.com.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import stu01.com.bean.User;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
@Configuration
@ConfigurationProperties(prefix = "user-info")
@EnableConfigurationProperties(User.class)
public class Myconfig {
@Autowired
ApplicationContext applicationContext;
@Bean
public User getUser() throws IOException {
applicationContext.getBean("myconfig");
boolean haha = applicationContext.containsBean("user");
boolean hehe = applicationContext.containsBean("hehe");
System.out.println("haha:" + haha);//true
System.out.println("hehe:" + hehe);//true
User user = (User) applicationContext.getBean("user");
System.out.println(user.toString());
return user;
}
}三、自動配置原理入門
3.1 引導(dǎo)加載自動配置類

點擊程序的啟動類上的@SpringBootApplication注解,會出現(xiàn)下面的類,上面被加上了幾個注解

@SpringBootConfiguration:代表當前是一個配置類; @ComponentScan:指定掃描哪些,Spring注解;
@EnableAutoConfiguration:自動配置的注解
3.1.1 @AutoConfigurationPackage
點擊注解@EnableAutoConfiguration,直到看到AutoConfigurationPackage自動配置包:指定了默認的包規(guī)則
@Import(AutoConfigurationPackages.Registrar.class) //給容器中導(dǎo)入一個組件
public @interface AutoConfigurationPackage {}
//利用Registrar給容器中導(dǎo)入一系列組件
//將指定的一個包下的所有組件導(dǎo)入進來?MainApplication 所在包下。3.1.2 @Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);給容器中批量導(dǎo)入一些組件
2、調(diào)用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)獲取到所有需要導(dǎo)入到容器中的配置類
3、利用工廠加載 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的組件
4、從META-INF/spring.factories位置來加載一個文件。
默認掃描我們當前系統(tǒng)里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
文件里面寫死了spring-boot一啟動就要給容器中加載的所有配置類

3.2 按需開啟自動配置項
雖然我們127個場景的所有自動配置啟動的時候默認全部加載。xxxxAutoConfiguration 按照條件裝配規(guī)則(@Conditional),最終會按需配置。
自動配置是非侵入性的。在任何時候,您都可以開啟定義自己的配置來替換自動配置的特定部分。例如你想添加自己的DataSourceBean,則默認的嵌入式數(shù)據(jù)庫支持會默認退出。
如果您需要了解當前正在應(yīng)用哪些自動配置以及原因,請使用–debug開關(guān)啟動您的應(yīng)用程序。這樣做可以為選擇的核心記錄器啟用調(diào)試日志,并將條件報告記錄到控制臺。
3.2.1 禁用特定的自動配置類
如果您發(fā)現(xiàn)正在應(yīng)用您不想要的特定自動配置類,您可以使用 exclude 屬性@SpringBootApplication來禁用它們,如下例所示:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication {
}3.3 修改默認配置
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有這個類型組件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中沒有這個名字 multipartResolver 的組件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//給@Bean標注的方法傳入了對象參數(shù),這個參數(shù)的值就會從容器中找。
//SpringMVC multipartResolver。防止有些用戶配置的文件上傳解析器不符合規(guī)范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
給容器中加入了文件上傳解析器;SpringBoot默認會在底層配好所有的組件。但是如果用戶自己配置了以用戶的優(yōu)先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}3.3.1 總結(jié):
- SpringBoot先加載所有的自動配置類 xxxxxAutoConfiguration
- 每個自動配置類按照條件進行生效,默認都會綁定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件進行了綁定
- 生效的配置類就會給容器中裝配很多組件
- 只要容器中有這些組件,相當于這些功能就有了
- 定制化配置
- 用戶直接自己@Bean替換底層的組件
- 用戶去看這個組件是獲取的配置文件什么值就去修改。
四、最佳實踐&開發(fā)技巧
4.1 Lombok
簡化JavaBean開發(fā)
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>idea中搜索安裝lombok插件
===============================簡化JavaBean開發(fā)===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {
private String name;
private Integer age;
private Pet pet;
public User(String name,Integer age){
this.name = name;
this.age = age;
}
}
================================簡化日志開發(fā)===================================
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(@RequestParam("name") String name){
log.info("請求進來了....");
return "Hello, Spring Boot 2!"+"你好:"+name;
}
}4.2 dev-tools 無須重啟部署項目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>項目或者頁面修改以后:Ctrl+F9;
到此這篇關(guān)于SpringBoot自動配置的原理詳解的文章就介紹到這了,更多相關(guān)SpringBoot自動配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)統(tǒng)計文件夾下所有文件的字數(shù)
這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)統(tǒng)計文件夾下所有文件的字數(shù),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習一下2024-03-03
Spring動態(tài)數(shù)據(jù)源實現(xiàn)讀寫分離詳解
這篇文章主要為大家詳細介紹了Spring動態(tài)數(shù)據(jù)源實現(xiàn)讀寫分離,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
Springboot項目平滑關(guān)閉及自動化關(guān)閉腳本
這篇文章主要為大家詳細介紹了Springboot項目平滑關(guān)閉及自動化關(guān)閉腳本,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
關(guān)于SpringCloud整合RabbitMQ的實例
這篇文章主要介紹了關(guān)于SpringCloud整合RabbitMQ的實例,消息隊列是指利用高效可靠的消息傳遞機制進行與平臺無關(guān)的數(shù)據(jù)交流,并基于數(shù)據(jù)通信來進行分布式系統(tǒng)的集成,是在消息的傳輸過程中保存消息的容器,需要的朋友可以參考下2023-07-07
java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處
java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處,需要的朋友可以參考一下2013-03-03
Java Runnable線程傳參,實現(xiàn)讓run訪問參數(shù)
這篇文章主要介紹了Java Runnable線程傳參,實現(xiàn)讓run訪問參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
一篇文章帶你理解Java Spring三級緩存和循環(huán)依賴
這篇文章主要介紹了淺談Spring 解決循環(huán)依賴必須要三級緩存嗎,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2021-09-09
SocketIo+SpringMvc實現(xiàn)文件的上傳下載功能
這篇文章主要介紹了SocketIo+SpringMvc實現(xiàn)文件的上傳下載功能,socketIo不僅可以用來做聊天工具,也可以實現(xiàn)局域網(wǎng)。文中給出了實現(xiàn)代碼,需要的朋友可以參考下2018-08-08

