目錄
1. 配置優先級
2. Bean管理
?????????2.1 獲取Bean
2.2 Bean作用域
面試題:@Lazy是如何解決循環依賴問題的?
2.3 第三方Bean
3. SpringBoot原理
3.1 起步依賴
3.2 自動配置
3.2.1 概述
3.2.2 自動配置的原理及常見方案
3.2.2.1 概述
3.2.2.2 方案一
3.2.2.3 方案二
3.2.3 原理分析
3.2.3.1 源碼跟蹤
3.2.3.2 @Conditional
4. Web后端開發總結
今天所要學習的是web后端開發的最后一個篇章springboot原理篇,主要偏向于底層原理。
我們今天的安排包括這么三個部分:
-
配置優先級:Springboot項目當中屬性配置的常見方式以及配置的優先級
-
Bean的管理
-
剖析Springboot的底層原理
1. 配置優先級
在我們前面的課程當中,我們已經講解了SpringBoot項目當中支持的三類配置文件:
-
application.properties
-
application.yml
-
application.yaml
在SpringBoot項目當中,我們要想配置一個屬性,可以通過這三種方式當中的任意一種來配置都可以,那么如果項目中同時存在這三種配置文件,且都配置了同一個屬性,如:Tomcat端口號,到底哪一份配置文件生效呢?
- application.properties
server.port=8081
- application.yml
server:port: 8082
- application.yaml
server:port: 8082
我們啟動SpringBoot程序,測試下三個配置文件中哪個Tomcat端口號生效:
-
properties、yaml、yml三種配置文件同時存在
properties、yaml、yml三種配置文件,優先級最高的是properties
配置文件優先級排名(從高到低):
properties配置文件
yml配置文件
yaml配置文件
注意事項:雖然springboot支持多種格式配置文件,但是在項目開發時,推薦統一使用一種格式的配置。(yml是主流)
在SpringBoot項目當中除了以上3種配置文件外,SpringBoot為了增強程序的擴展性,除了支持配置文件的配置方式以外,還支持另外兩種常見的配置方式:
-
Java系統屬性配置 (格式: -Dkey=value)
-Dserver.port=9000
??????2.命令行參數 (格式:--key=value)
--server.port=10010
那在idea當中運行程序時,如何來指定Java系統屬性和命令行參數呢?
-
編輯啟動程序的配置信息
重啟服務,同時配置Tomcat端口(三種配置文件、系統屬性、命令行參數),測試哪個Tomcat端口號生效:
刪除命令行參數配置,重啟SpringBoot服務:
優先級: 命令行參數 > 系統屬性參數 > properties參數 > yml參數 > yaml參數
如果項目已經打包上線了,這個時候我們又如何來設置Java系統屬性和命令行參數:
下面我們來演示下打包程序運行時指定Java系統屬性和命令行參數:
-
執行maven打包指令package,把項目打成jar文件
-
使用命令:java -jar 方式運行jar文件程序
項目打包:
測試人員端口8080端口被占用時,可以設置臨時端口號。
運行jar程序:
-
同時設置Java系統屬性和命令行參數
- 僅設置Java系統屬性
注意事項:
Springboot項目進行打包時,需要引入插件 spring-boot-maven-plugin (基于官網骨架創建項目,會自動添加該插件)
在SpringBoot項目當中,常見的屬性配置方式有5種, 3種配置文件,加上2種外部屬性的配置(Java系統屬性、命令行參數)。通過以上的測試,我們也得出了優先級(從低到高):
-
application.yaml(忽略)
-
application.yml
-
application.properties
-
java系統屬性(-Dxxx=xxx)
-
命令行參數(--xxx=xxx)
2. Bean管理
前面我們已經學習過Spring當中提供的注解@Component以及它的三個衍生注解(@Controller、@Service、@Repository)來聲明IOC容器中的bean對象,同時我們也學習了如何為應用程序注入運行時所需要依賴的bean對象,也就是依賴注入DI。
我們今天主要學習IOC容器中Bean的其他使用細節,主要學習以下三方面:
-
如何從IOC容器中手動的獲取到bean對象
-
bean的作用域配置
-
管理第三方的bean對象
接下來我們先來學習第一方面,從IOC容器中獲取bean對象。
2.1 獲取Bean
默認情況下,SpringBoot項目在啟動的時候會自動的創建IOC容器(也稱為Spring容器),并且在啟動的過程當中會自動的將bean對象都創建好,存放在IOC容器當中。應用程序在運行時需要依賴什么bean對象,就直接進行依賴注入就可以了。
而在Spring容器中提供了一些方法,可以主動從IOC容器中獲取到bean對象,下面介紹3種常用方式:
-
根據name獲取bean
Object getBean(String name)
???????2.根據類型獲取bean
<T> T getBean(Class<T> requiredType)
???????3.根據name獲取bean(帶類型轉換)
<T> T getBean(String name, Class<T> requiredType)
思考:要從IOC容器當中來獲取到bean對象,需要先拿到IOC容器對象,怎么樣才能拿到IOC容器呢?
-
想獲取到IOC容器,直接將IOC容器對象注入進來就可以了,相當于創建Controller層的構造器
控制器:DeptController
@RestController
@RequestMapping("/depts")
public class DeptController {@Autowiredprivate DeptService deptService;public DeptController(){System.out.println("DeptController constructor ....");}@GetMappingpublic Result list(){List<Dept> deptList = deptService.list();return Result.success(deptList);}@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id) {deptService.delete(id);return Result.success();}@PostMappingpublic Result save(@RequestBody Dept dept){deptService.save(dept);return Result.success();}
}
業務實現類:DeptServiceImpl
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Overridepublic List<Dept> list() {List<Dept> deptList = deptMapper.list();return deptList;}@Overridepublic void delete(Integer id) {deptMapper.delete(id);}@Overridepublic void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}
}
Mapper接口:
@Mapper
public interface DeptMapper {//查詢全部部門數據@Select("select * from dept")List<Dept> list();//刪除部門@Delete("delete from dept where id = #{id}")void delete(Integer id);//新增部門@Insert("insert into dept(name, create_time, update_time) values (#{name},#{createTime},#{updateTime})")void save(Dept dept);
}
測試類:
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {@Autowiredprivate ApplicationContext applicationContext; //IOC容器對象//獲取bean對象@Testpublic void testGetBean(){//根據bean的名稱獲取,強轉為DeptController類型DeptController bean1 = (DeptController) applicationContext.getBean("deptController");System.out.println(bean1);//根據bean的類型獲取DeptController bean2 = applicationContext.getBean(DeptController.class);System.out.println(bean2);//根據bean的名稱 及 類型獲取DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);System.out.println(bean3);}
}
程序運行后控制臺日志:
問題:輸出的bean對象地址值是一樣的,說明IOC容器當中的bean對象有幾個?
答案:只有一個。 (默認情況下,IOC中的bean對象是單例)
那么能不能將bean對象設置為非單例的(每次獲取的bean都是一個新對象)?
可以,在下一個知識點(bean作用域)中講解。
2.2 Bean作用域
在前面我們提到的IOC容器當中,默認bean對象是單例模式(只有一個實例對象)。那么如何設置bean對象為非單例呢?需要設置bean的作用域。
在Spring中支持五種作用域,后三種在web環境才生效:
作用域 | 說明 |
---|---|
singleton | 容器內同名稱的bean只有一個實例(單例)(默認) |
prototype | 每次使用該bean時會創建新的實例(非單例) |
request | 每個請求范圍內會創建新的實例(web環境中,了解) |
session | 每個會話范圍內會創建新的實例(web環境中,了解) |
application | 每個應用范圍內會創建新的實例(web環境中,了解) |
知道了bean的5種作用域了,我們要怎么去設置一個bean的作用域呢?
-
可以借助Spring中的@Scope注解來進行配置作用域
1). 測試一
-
控制器
//默認bean的作用域為:singleton (單例)
@Lazy //延遲加載(第一次使用bean對象時,才會創建bean對象并交給ioc容器管理)
@RestController
@RequestMapping("/depts")
public class DeptController {@Autowiredprivate DeptService deptService;public DeptController(){System.out.println("DeptController constructor ....");}//省略其他代碼...
}
- 測試類
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {@Autowiredprivate ApplicationContext applicationContext; //IOC容器對象//bean的作用域@Testpublic void testScope(){for (int i = 0; i < 10; i++) {DeptController deptController = applicationContext.getBean(DeptController.class);System.out.println(deptController);}}
}
重啟SpringBoot服務,運行測試方法,查看控制臺打印的日志:
注意事項:
IOC容器中的bean默認使用的作用域:singleton (單例)
默認singleton的bean是在容器啟動時就會被創建,但是我們可以使用@Lazy注解來延遲初始化(延遲到第一次使用時bean對象時,才會創建bean對象并交給ioc容器管理)
2). 測試二
修改控制器DeptController代碼:
@Scope("prototype") //bean作用域為非單例
@Lazy //延遲加載
@RestController
@RequestMapping("/depts")
public class DeptController {@Autowiredprivate DeptService deptService;public DeptController(){System.out.println("DeptController constructor ....");}//省略其他代碼...
}
重啟SpringBoot服務,再次執行測試方法,查看控制吧打印的日志:
注意事項:
prototype的bean,每一次使用該bean的時候都會創建一個新的實例
實際開發當中,絕大部分的Bean是單例的,也就是說絕大部分Bean不需要配置scope屬性
面試題:@Lazy是如何解決循環依賴問題的?
一、前置知識:Spring循環依賴的核心概念
1.什么是Spring循環依賴?
循環依賴指的是兩個或多個Bean互相依賴對方才能完成初始化,形成"閉環依賴鏈"。例如:
ServiceA
?的構造函數 / 屬性需要注入?ServiceB
;ServiceB
?的構造函數 / 屬性需要注入?ServiceA
;
形成?ServiceA ←→ ServiceB
?的閉環。
2.Spring默認如何處理循環依賴?
Spring對單例Bean的"屬性注入"(setter注入/字段注入)有默認的循環依賴解決方案,核心是通過“三級緩存”實現:
- 一級緩存(singletonObjects):存儲完全初始化完成的單例 Bean;
- 二級緩存(earlySingletonObjects):存儲 “提前暴露的未完全初始化的 Bean”(僅實例化完成,未注入屬性和執行初始化方法);
- 三級緩存(singletonFactories):存儲 “Bean 工廠”(用于生成未完全初始化的 Bean 實例)。
默認解決邏輯(以A->B->A為例):
- 初始化?
A
:實例化?A
(調用無參構造)→ 將?A
?的 “工廠” 放入三級緩存 → 準備注入?B
(發現?B
?未創建); - 初始化?
B
:實例化?B
?→ 將?B
?的 “工廠” 放入三級緩存 → 準備注入?A
(從三級緩存取出?A
?的工廠,生成未完全初始化的?A
,放入二級緩存); B
?注入?A
(未完全初始化的?A
)→?B
?初始化完成,放入一級緩存;A
?注入?B
(完全初始化的?B
)→?A
?初始化完成,放入一級緩存。
但注意:Spring 默認方案有局限性—— 僅支持 “單例 Bean + 屬性注入”,無法解決以下場景的循環依賴:
- 多例 Bean(prototype scope):Spring 不緩存多例 Bean,每次獲取都新建,無法提前暴露實例;
- 構造函數注入:Bean 實例化時必須傳入依賴,若依賴未創建,無法完成實例化,三級緩存無從談起;
- 非單例 Bean(如 request/session scope):緩存機制不適用。
而?@Lazy
?正是為解決這些 “默認方案無法覆蓋” 的循環依賴場景而生。
二、@Lazy 注解的核心作用:延遲初始化(延遲創建 Bean 實例)
@Lazy
?是 Spring 的核心注解之一,作用是將 Bean 的初始化時機從 “Spring 容器啟動時” 推遲到 “第一次被使用時”(即 “懶加載”)。
默認情況下,Spring 容器啟動時會創建所有 “單例 Bean”(提前初始化,確保啟動時暴露配置錯誤);而添加?@Lazy
?后,Bean 會被標記為 “延遲初始化”,容器啟動時不創建實例,直到代碼中第一次調用?getBean()
(或通過依賴注入觸發獲取)時才創建。
關鍵機制:生成 “代理對象” 代替 “真實實例”
@Lazy
?解決循環依賴的核心技巧,并非 “直接創建真實 Bean”,而是在循環依賴鏈條中,用一個 “代理對象” 暫時替代未創建的真實 Bean,先滿足依賴注入的 “形式要求”,打破循環鏈條,待后續真實 Bean 初始化時再替換。
舉個具體例子:ServiceA
?構造函數注入?ServiceB
,ServiceB
?構造函數注入?ServiceA
(構造函數循環依賴,默認無法解決),添加?@Lazy
?后流程如下:
// ServiceA:構造函數注入 ServiceB
@Service
public class ServiceA {private final ServiceB serviceB;// 對 ServiceB 注入添加 @Lazypublic ServiceA(@Lazy ServiceB serviceB) {this.serviceB = serviceB;System.out.println("ServiceA 實例化完成");}
}// ServiceB:構造函數注入 ServiceA
@Service
public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;System.out.println("ServiceB 實例化完成");}
}
添加 @Lazy 后的初始化流程(打破循環):
- Spring 容器啟動,開始初始化?
ServiceA
(單例 Bean,默認提前初始化,但因依賴?ServiceB
,先處理?ServiceB
); - 初始化?
ServiceB
:ServiceB
?的構造函數需要?ServiceA
,但?ServiceA
?尚未創建,此時進入循環 ——若沒有 @Lazy,Spring 會直接拋出循環依賴異常; - 但?
ServiceA
?注入?ServiceB
?時添加了?@Lazy
:Spring 不會立即創建?ServiceB
?的真實實例,而是生成一個?ServiceB
?的代理對象(基于 JDK 動態代理或 CGLIB,取決于?ServiceB
?是否實現接口); - 用?
ServiceB
?的代理對象注入?ServiceA
?的構造函數:ServiceA
?拿到代理對象后,成功完成實例化(此時?ServiceA
?中的?serviceB
?是代理,非真實實例); ServiceA
?實例化完成后,注入?ServiceB
?的構造函數:ServiceB
?拿到真實的?ServiceA
?實例,成功完成實例化;- 后續當代碼第一次調用?
ServiceA.serviceB
?的方法時(如?serviceA.getServiceB().doSomething()
),代理對象會觸發?ServiceB
?真實實例的創建,此時?ServiceB
?已存在真實的?ServiceA
?依賴,無需再循環。
總結
@Lazy
?解決循環依賴的核心邏輯是:通過 “延遲初始化” 和 “代理對象占位”,打破 “依賴必須提前創建” 的循環鏈條—— 在循環依賴的某個節點,用代理對象暫時替代未創建的真實 Bean,先滿足當前 Bean 的初始化需求,待后續真實 Bean 被使用時再創建,從而解決 Spring 默認方案無法覆蓋的 “構造函數注入”“多例 Bean” 等循環依賴場景。
其本質是 “時間換空間”:將真實 Bean 的創建時機從 “初始化階段” 推遲到 “第一次使用階段”,用延遲初始化的代價,換取循環依賴的解決。
2.3 第三方Bean
學習完bean的獲取、bean的作用域之后,接下來我們再來學習第三方bean的配置。
之前我們所配置的bean,像controller、service、dao三層體系下編寫的類,這些類都是我們在項目當中自己定義的類(自定義類)。當我們要聲明這些bean,也非常簡單,我們只需要在類上加上@Component以及它的這三個衍生注解(@Controller、@Service、@Repository),就可以來聲明這個bean對象了。 但是在我們項目開發當中,還有一種情況就是這個類它不是我們自己編寫的,而是我們引入的第三方依賴當中提供的。
dom4j就是第三方組織提供的,用來解析xml文件的依賴。 dom4j中的SAXReader類就是第三方編寫的。
當我們需要使用到SAXReader對象時,直接進行依賴注入是不是就可以了呢?
-
按照我們之前的做法,需要在SAXReader類上添加一個注解@Component(將當前類交給IOC容器管理)
結論:第三方提供的類是只讀的。無法在第三方類上添加@Component注解或衍生注解。
那么我們應該怎樣使用并定義第三方的bean呢?
-
如果要管理的bean對象來自于第三方(不是自定義的),是無法用@Component 及衍生注解聲明bean的,就需要用到@Bean注解。
解決方案1:在啟動類上添加@Bean標識的方法
@SpringBootApplication
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}//聲明第三方bean@Bean //將當前方法的返回值對象交給IOC容器管理, 成為IOC容器beanpublic SAXReader saxReader(){return new SAXReader();}
}
xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<emp><name>Tom</name><age>18</age>
</emp>
測試類:
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {@Autowiredprivate SAXReader saxReader;//第三方bean的管理@Testpublic void testThirdBean() throws Exception {Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));Element rootElement = document.getRootElement();String name = rootElement.element("name").getText();String age = rootElement.element("age").getText();System.out.println(name + " : " + age);}//省略其他代碼...
}
重啟SpringBoot服務,執行測試方法后,控制臺輸出日志:
Tom : 18
說明:以上在啟動類中聲明第三方Bean的作法,不建議使用(項目中要保證啟動類的純粹性),要把聲明第三方Bean的做法放到單獨定義的配置類中。
解決方案2:在配置類中定義@Bean標識的方法
/*** 管理第三方bean*/
@Slf4j
@Configuration //聲明該類是配置類,并交由IOC容器管理
public class CommonConfig {// @Autowired// private ServiceA serviceA;@Bean("SAXReader") //作用:程序啟動時,會執行該方法,并將方法的返回值對象交由IOC容器管理//bean的名字默認是方法名, 可以通過name|value屬性設置bean的名字//如果需要依賴注入其他bean對象,直接在形參列表聲明即可public SAXReader saxReader(ServiceA serviceA){log.info("創建saxReader對象...................");serviceA.add();return new SAXReader();}
}
要想在第三方bean對象的代碼中使用ServiceA中的add()方法,第一種方式是在前面聲明并用@Autowired注解進行依賴注入,第二種方式是將ServiceA直接作為參數傳入到saxReader中。
在方法上加上一個@Bean注解,Spring 容器在啟動的時候,它會自動的調用這個方法,并將方法的返回值聲明為Spring容器當中的Bean對象。
注意事項 :
通過@Bean注解的name或value屬性可以聲明bean的名稱,如果不指定,默認bean的名稱就是方法名。
如果第三方bean需要依賴其它bean對象,直接在bean定義方法中設置形參即可,容器會根據類型自動裝配。
關于Bean大家只需要保持一個原則:
-
如果是在項目當中我們自己定義的類,想將這些類交給IOC容器管理,我們直接使用@Component以及它的衍生注解來聲明就可以。
-
如果這個類它不是我們自己定義的,而是引入的第三方依賴當中提供的類,而且我們還想將這個類交給IOC容器管理。此時我們就需要在配置類中定義一個方法,在方法上加上一個@Bean注解,通過這種方式來聲明第三方的bean對象。
3. SpringBoot原理
通過前面的學習我們會發現基于SpringBoot進行web程序的開發是非常簡單、非常高效的。
SpringBoot使我們能夠集中精力地去關注業務功能的開發,而不用過多地關注框架本身的配置使用。而我們前面所講解的都是面向應用層面的技術,接下來我們開始學習SpringBoot的原理,這部分內容偏向于底層的原理分析。
在剖析SpringBoot的原理之前,我們先來快速回顧一下我們前面所講解的Spring家族的框架。
Spring是目前世界上最流行的Java框架,它可以幫助我們更加快速、更加容易的來構建Java項目。而在Spring家族當中提供了很多優秀的框架,而所有的框架都是基于一個基礎框架的SpringFramework(也就是Spring框架)。而前面我們也提到,如果我們直接基于Spring框架進行項目的開發,會比較繁瑣。
這個繁瑣主要體現在兩個地方:
-
在pom.xml中依賴配置比較繁瑣,在項目開發時,需要自己去找到對應的依賴,還需要找到依賴它所配套的依賴以及對應版本,否則就會出現版本沖突問題。
-
在使用Spring框架進行項目開發時,需要在Spring的配置文件中做大量的配置,這就造成Spring框架入門難度較大,學習成本較高。
基于Spring存在的問題,官方在Spring框架4.0版本之后,又推出了一個全新的框架:SpringBoot。
通過 SpringBoot來簡化Spring框架的開發(是簡化不是替代)。我們直接基于SpringBoot來構建Java項目,會讓我們的項目開發更加簡單,更加快捷。
SpringBoot框架之所以使用起來更簡單更快捷,是因為SpringBoot框架底層提供了兩個非常重要的功能:一個是起步依賴,一個是自動配置。
通過SpringBoot所提供的起步依賴,就可以大大的簡化pom文件當中依賴的配置,從而解決了Spring框架當中依賴配置繁瑣的問題。
通過自動配置的功能就可以大大的簡化框架在使用時bean的聲明以及bean的配置。我們只需要引入程序開發時所需要的起步依賴,項目開發時所用到常見的配置都已經有了,我們直接使用就可以了。
簡單回顧之后,接下來我們來學習下SpringBoot的原理。其實學習SpringBoot的原理就是來解析SpringBoot當中的起步依賴與自動配置的原理。我們首先來學習SpringBoot當中起步依賴的原理。
3.1 起步依賴
假如我們沒有使用SpringBoot,用的是Spring框架進行web程序的開發,此時我們就需要引入web程序開發所需要的一些依賴。
spring-webmvc依賴:這是Spring框架進行web程序開發所需要的依賴
servlet-api依賴:Servlet基礎依賴
jackson-databind依賴:JSON處理工具包
如果要使用AOP,還需要引入aop依賴、aspect依賴
項目中所引入的這些依賴,還需要保證版本匹配,否則就可能會出現版本沖突問題。
如果我們使用了SpringBoot,就不需要像上面這么繁瑣的引入依賴了。我們只需要引入一個依賴就可以了,那就是web開發的起步依賴:springboot-starter-web
為什么我們只需要引入一個web開發的起步依賴,web開發所需要的所有的依賴都有了呢?
-
因為Maven的依賴傳遞。
在SpringBoot給我們提供的這些起步依賴當中,已提供了當前程序開發所需要的所有的常見依賴(官網地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters)。
比如:springboot-starter-web,這是web開發的起步依賴,在web開發的起步依賴當中,就集成了web開發中常見的依賴:json、web、webmvc、tomcat等。我們只需要引入這一個起步依賴,其他的依賴都會自動的通過Maven的依賴傳遞進來。
結論:起步依賴的原理就是Maven的依賴傳遞。
3.2 自動配置
我們講解了SpringBoot當中起步依賴的原理,就是Maven的依賴傳遞。接下來我們解析下自動配置的原理,我們要分析自動配置的原理,首先要知道什么是自動配置。
3.2.1 概述
SpringBoot的自動配置就是當Spring容器啟動后,一些配置類、bean對象就自動存入到了IOC容器中,不需要我們手動去聲明,從而簡化了開發,省去了繁瑣的配置操作。
比如:我們要進行事務管理、要進行AOP程序的開發,此時就不需要我們再去手動的聲明這些bean對象了,我們直接使用就可以從而大大的簡化程序的開發,省去了繁瑣的配置操作。
下面我們打開idea,一起來看下自動配置的效果:
-
運行SpringBoot啟動類
我們可以看到有兩個CommonConfig,在第一個CommonConfig類中定義了一個bean對象,bean對象的名字叫reader。
在第二個CommonConfig中它的bean名字叫commonConfig,為什么還會有這樣一個bean對象呢?原因是CommonConfig配置類上添加了一個注解@Configuration,而@Configuration底層就是@Component
所以配置類最終也是SpringIOC容器當中的一個bean對象
在IOC容器中除了我們自己定義的bean以外,還有很多配置類,這些配置類都是SpringBoot在啟動的時候加載進來的配置類。這些配置類加載進來之后,它也會生成很多的bean對象。
比如:配置類GsonAutoConfiguration里面有一個bean,bean的名字叫gson,它的類型是Gson。
com.google.gson.Gson是谷歌包中提供的用來處理JSON格式數據的。
當我們想要使用這些配置類中生成的bean對象時,可以使用@Autowired就自動注入了:
@SpringBootTest
public class AutoConfigurationTests {@Autowiredprivate Gson gson;@Testpublic void testJson(){String json = gson.toJson(Result.success());System.out.println(json);}
}
問題:在當前項目中我們并沒有聲明谷歌提供的Gson這么一個bean對象,然后我們卻可以通過@Autowired從Spring容器中注入bean對象,那么這個bean對象怎么來的?
答案:SpringBoot項目在啟動時通過自動配置完成了bean對象的創建。
體驗了SpringBoot的自動配置了,下面我們就來分析自動配置的原理。其實分析自動配置原理就是來解析在SpringBoot項目中,在引入依賴之后是如何將依賴jar包當中所定義的配置類以及bean加載到SpringIOC容器中的。
3.2.2 自動配置的原理及常見方案
3.2.2.1 概述
我們知道什么是自動配置之后,接下來我們要剖析自動配置的原理。解析自動配置的原理就是分析在 SpringBoot項目當中,我們引入對應的依賴之后,是如何將依賴jar包當中所提供的bean以及配置類直接加載到當前項目的SpringIOC容器當中的。
接下來,我們就直接通過代碼來分析自動配置原理。
1、在SpringBoot項目 spring-boot-web-config2 工程中,通過坐標引入itheima-utils依賴
package com.example;import org.springframework.stereotype.Component;@Component
public class TokenParser {public void parse(){System.out.println("TokenParser ... parse ...");}}
package com.example;public class HeaderParser {public void parse(){System.out.println("HeaderParser ... parse ...");}}
package com.example;public class HeaderGenerator {public void generate(){System.out.println("HeaderGenerator ... generate ...");}}
2、在測試類中,添加測試方法,這個方法是獲取IOC容器中的bean對象
package com.itheima;@SpringBootTest
public class TestAutoConfig {@Autowiredprivate ApplicationContext context;@Testpublic void testGetTokenParser(){TokenParser tokenParser = context.getBean(TokenParser.class);System.out.println("tokenParser = " + tokenParser);}@Testpublic void testGetHeaderParser(){HeaderParser headerParser = context.getBean(HeaderParser.class);System.out.println("headerParser = " + headerParser);}@Testpublic void testGetHeaderGenerator(){HeaderGenerator headerGenerator = context.getBean(HeaderGenerator.class);System.out.println("headerGenerator = " + headerGenerator);}
}
3、執行測試方法
異常信息以第一個TokenParse為例
異常信息描述: 沒有com.example.TokenParse類型的bean
說明:在Spring容器中沒有找到com.example.TokenParse類型的bean對象
思考:引入進來的第三方依賴當中的bean以及配置類為什么沒有生效?
-
原因在我們之前講解IOC的時候有提到過,在類上添加@Component注解來聲明bean對象時,還需要保證@Component注解能被Spring的組件掃描到。
-
SpringBoot項目中的@SpringBootApplication注解,具有包掃描的作用,但是它只會掃描啟動類所在的當前包以及子包。
-
當前包:com.itheima, 第三方依賴中提供的包:com.example(掃描不到)
那么如何解決以上問題的呢?
-
方案1:@ComponentScan 組件掃描
-
方案2:@Import 導入(使用@Import導入的類會被Spring加載到IOC容器中)
3.2.2.2 方案一
@ComponentScan組件掃描
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要掃描的包
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}
}
重新執行測試方法,控制臺日志輸出:
大家可以想象一下,如果采用以上這種方式來完成自動配置,那我們進行項目開發時,當需要引入大量的第三方的依賴,就需要在啟動類上配置N多要掃描的包,這種方式會很繁瑣。而且這種大面積的掃描性能也比較低。
缺點:
使用繁瑣
性能低
結論:SpringBoot中并沒有采用以上這種方案。
3.2.2.3 方案二
@Import導入
-
導入形式主要有以下幾種:
-
導入普通類
-
導入配置類
-
導入ImportSelector接口實現類
-
1). 使用@Import導入普通類:
@Import(TokenParser.class) //導入的類會被Spring加載到IOC容器中
@SpringBootApplication
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}
}
重新執行測試方法,控制臺日志輸出:
2). 使用@Import導入配置類:
- 配置類
@Configuration
public class HeaderConfig {@Beanpublic HeaderParser headerParser(){return new HeaderParser();}@Beanpublic HeaderGenerator headerGenerator(){return new HeaderGenerator();}
}
- 啟動類
@Import(HeaderConfig.class) //導入配置類
@SpringBootApplication
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}
}
- 測試類
@SpringBootTest
public class AutoConfigurationTests {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testHeaderParser(){System.out.println(applicationContext.getBean(HeaderParser.class));}@Testpublic void testHeaderGenerator(){System.out.println(applicationContext.getBean(HeaderGenerator.class));}//省略其他代碼...
}
執行測試方法:
3). 使用@Import導入ImportSelector接口實現類:
-
ImportSelector接口實現類
public class MyImportSelector implements ImportSelector {public String[] selectImports(AnnotationMetadata importingClassMetadata) {//返回值字符串數組(數組中封裝了全限定名稱的類)return new String[]{"com.example.HeaderConfig"};}
}
- 啟動類
@Import(MyImportSelector.class) //導入ImportSelector接口實現類
@SpringBootApplication
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}
}
執行測試方法:
我們使用@Import注解通過這三種方式都可以導入第三方依賴中所提供的bean或者是配置類。
思考:如果基于以上方式完成自動配置,當要引入一個第三方依賴時,是不是還要知道第三方依賴中有哪些配置類和哪些Bean對象?
-
答案:是的。 (對程序員來講,很不友好,而且比較繁瑣)
如何理解這句話呢?
當我們手動用@Import去導入第三方依賴里的Bean或配置類時,必須清除第三方依賴中具體有哪些類需要被Spring容器管理。
比如:有一個第三方日志庫,它內部有負責日志格式配置的LogConfig類、處理日志輸出的LogHandler,我們要手動導入這些組件,就需要先了解日志庫源碼或文檔,找到這些類的全限定名(比如com.third.log.LogConfig、com.third.log.LogHandler),然后才能寫@Import相關邏輯。
這種方式對程序員 “不友好且繁瑣”,因為:
- 要去查閱第三方依賴的內部結構(看源碼、查文檔),成本高;
- 一旦第三方依賴升級,內部類的結構變化(比如類名、包名改動),我們手動寫的?
@Import
?邏輯就可能失效,需要同步修改。
但是第三方依賴自身最清楚有哪些Bean和配置類,那最好的方式就是讓第三方依賴自己來定義"要給Spring容器導入哪些類"。常見的方式就是第三方依賴提供一個以@Enablexxxx開頭的注解,這個類內部封裝了@Import邏輯(可能是通過?ImportSelector
、直接導入配置類等方式)。當我們在項目中使用這個第三方依賴時,只需要在啟動類上方添加@Enablexxx注解,就能把依賴里需要的Bean和配置類導入Spring容器中,我們無序再關心內部細節。
思考:當我們要使用第三方依賴,依賴中到底有哪些bean和配置類,誰最清楚?
-
答案:第三方依賴自身最清楚。
結論:我們不用自己指定要導入哪些bean對象和配置類了,讓第三方依賴它自己來指定。
怎么讓第三方依賴自己指定bean對象和配置類?
-
比較常見的方案就是第三方依賴給我們提供一個注解,這個注解一般都以@EnableXxxx開頭的注解,注解中封裝的就是@Import注解
4). 使用第三方依賴提供的 @EnableXxxxx注解
-
第三方依賴中提供的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要導入哪些bean對象或配置類
public @interface EnableHeaderConfig {
}
- 在使用時只需在啟動類上加上@EnableXxxxx注解即可
@EnableHeaderConfig //使用第三方依賴提供的Enable開頭的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {public static void main(String[] args) {SpringApplication.run(SpringbootWebConfig2Application.class, args);}
}
執行測試方法:
以上四種方式都可以完成導入操作,但是第4種方式會更方便更優雅,而這種方式也是SpringBoot當中所采用的方式。
@EnableXxxx
?是對?@Import
?的 “場景化升級”
@Import
?是 Spring 提供的基礎導入工具,而?@EnableXxxx
?是基于?@Import
?封裝的場景化解決方案。在第三方依賴中,@EnableXxxx
?的優勢在于:
- 用清晰的語義簡化配置理解;
- 用內部封裝隱藏復雜實現;
- 用條件控制和擴展點提升靈活性;
- 用標準化設計統一生態體驗。
因此,第三方依賴更傾向于通過?@EnableXxxx
?提供配置入口,而不是讓用戶直接使用?@Import
—— 這本質上是 “封裝復雜性,暴露簡單性” 的設計思想體現。
3.2.3 原理分析
3.2.3.1 源碼跟蹤
前面我們講解了在項目中引入第三方依賴,如何加載第三方依賴中定義好的bean對象以及配置類,從而完成自動配置操作。下面我們通過源碼跟蹤的形式來剖析一下SpringBoot底層到底是如何完成自動配置的。
源碼跟蹤技巧:
在跟蹤框架源碼的時候,一定要抓住關鍵點,找到核心流程。一定不要從頭到尾一行代碼去看,一個方法的去研究,一定要找到關鍵流程,抓住關鍵點,先在宏觀上對整個流程或者整個原理有一個認識,有精力再去研究其中的細節。
要搞清楚SpringBoot的自動配置原理,要從SpringBoot啟動類上使用的核心注解@SpringBootApplication開始分析(ctrl點擊進去這個注解):
先來解釋一下元注解:
@Target
它用于指定被修飾的注解可以應用的目標范圍(如類、方法、字段等)。ElementType.TYPE表示注解只能用于類、接口(包括注解類型)或者,枚舉類型上。
@Retention
用于指定修飾的注解的保留策略,即注解在什么階段有效。RetentionPolicy.RUNTIME
?表示該注解會被保留到運行時階段,這樣在程序運行時可以通過反射等方式獲取到注解的信息。
@Documented
被此注解修飾的注解,在生成 Java 文檔時,會將該注解的相關信息包含到文檔中,方便開發者查看。
@Inherited
如果一個類使用了被?@Inherited
?修飾的注解,那么該類的子類會自動繼承這個注解。這在需要注解具有繼承性的場景下很有用。
接下來我們看一下除了元注解的第一個注解:@SpringBootConfiguration,沖進去之后:
@SpringBootConfiguration注解上使用了@Configuration,表明SpringBoot啟動類就是一個配置類。
@Indexed注解,是用來加速應用啟動的(不用關心)。
接下來再先看@ComponentScan注解:
@ComponentScan注解是用來進行組件掃描的,掃描啟動類所在的包及其子包下所有被@Component及其衍生注解聲明的類。
SpringBoot啟動類,之所以具備掃描包功能,就是因為包含了@ComponentScan注解。
最后我們來看看@EnableAutoConfiguration注解(自動配置核心注解)
使用@Import注解,導入了實現ImportSelector接口的實現類。
AutoConfigurationImportSelector類是ImportSelector接口的實現類。
可以右鍵看一下這個類接口實現類的實現繼承關系,AutoConfigurationImportSelector類中重寫了ImportSelector接口的selectImports()方法:
底層代碼從地下往上看,這個方法返回的是一個String[ ]類型,看返回值中重要的代碼就是倒數第二行返回的內容。
selectImports()方法底層調用getAutoConfigurationEntry()方法,獲取可自動配置的配置類信息集合
點擊此方法沖進去之后:
getAutoConfigurationEntry()方法通過調用getCandidateConfigurations(annotationMetadata, attributes)方法獲取在配置文件中配置的所有自動配置類的集合
getCandidateConfigurations方法的功能:
獲取所有基于 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`文件中配置類的集合
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`文件這兩個文件在哪里呢?
- ?通常在引入的起步依賴中,都有包含以上文件?
在前面給大家演示自動配置的時候,我們直接在測試類當中注入了一個叫gson的bean對象,進行JSON格式轉換。雖然我們沒有配置bean對象,但是我們是可以直接注入使用的。原因就是因為在自動配置類當中做了自動配置。到底是在哪個自動配置類當中做的自動配置呢?我們通過搜索來查詢一下。
在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
配置文件中指定了第三方依賴Gson的配置類:GsonAutoConfiguration
第三方依賴中提供的GsonAutoConfiguration
類:
在GsonAutoConfiguration
類上,添加了注解@AutoConfiguration
,通過查看源碼,可以明確:GsonAutoConfiguration
類是一個配置。
看到這里,大家就應該明白為什么可以完成自動配置了,原理就是在配置類中定義一個@Bean標識的方法,而Spring會自動調用配置類中使用@Bean標識的方法,并把方法的返回值注冊到IOC容器中。
自動配置源碼小結
自動配置原理源碼入口就是@SpringBootApplication注解,在這個注解中封裝了3個注解,分別是:
-
@SpringBootConfiguration
-
聲明當前類是一個配置類
-
-
@ComponentScan
-
進行組件掃描(SpringBoot中默認掃描的是啟動類所在的當前包及其子包)
-
-
@EnableAutoConfiguration
-
封裝了@Import注解(Import注解中指定了一個ImportSelector接口的實現類)
-
在實現類重寫的selectImports()方法,讀取當前項目下所有依賴jar包中
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
兩個文件里面定義的配置類(配置類中定義了@Bean注解標識的方法)。
-
-
當SpringBoot程序啟動時,就會加載配置文件當中所定義的配置類,并將這些配置類信息(類的全限定名)封裝到String類型的數組中,最終通過@Import注解將這些配置類全部加載到Spring的IOC容器中,交給IOC容器管理。
最后呢給大家拋出一個問題:在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中定義的配置類非常多,而且每個配置類中又可以定義很多的bean,那這些bean都會注冊到Spring的IOC容器中嗎?答案:并不是。 在聲明bean對象時,上面有加一個以
@Conditional
開頭的注解,這種注解的作用就是按照條件進行裝配,只有滿足條件之后,才會將bean注冊到Spring的IOC容器中(下面會詳細來講解)
3.2.3.2 @Conditional
我們在跟蹤SpringBoot自動配置的源碼的時候,在自動配置類聲明bean的時候,除了在方法上加了一個@Bean注解以外,還會經常用到一個注解,就是以Conditional開頭的這一類的注解。以Conditional開頭的這些注解都是條件裝配的注解。下面我們就來介紹下條件裝配注解。
@Conditional注解:
-
作用:按照一定的條件進行判斷,在滿足給定條件后才會注冊對應的bean對象到Spring的IOC容器中。
-
位置:方法、類
-
@Conditional本身是一個父注解,派生出大量的子注解:
-
@ConditionalOnClass:判斷環境中有對應字節碼文件,才注冊bean到IOC容器。
-
@ConditionalOnMissingBean:判斷環境中沒有對應的bean(類型或名稱),才注冊bean到IOC容器。
-
@ConditionalOnProperty:判斷配置文件中有對應屬性和值,才注冊bean到IOC容器。
-
下面我們通過代碼來演示下Conditional注解的使用:
-
@ConditionalOnClass注解
@Configuration
public class HeaderConfig {@Bean@ConditionalOnClass(name="io.jsonwebtoken.Jwts")//環境中存在指定的這個類,才會將該bean加入IOC容器public HeaderParser headerParser(){return new HeaderParser();}//省略其他代碼...
}
也就是說在pom.xml文件中配置指定類的文件路徑,才會生成下面這個類的Bean對象
- pom.xml
<!--JWT令牌-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
- 測試類
@SpringBootTest
public class AutoConfigurationTests {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testHeaderParser(){System.out.println(applicationContext.getBean(HeaderParser.class));}//省略其他代碼...
}
執行testHeaderParser()測試方法:
因為 io.jsonwebtoken.Jwts?字節碼文件在啟動SpringBoot程序時已存在,所以創建HeaderParser對象并注冊到IOC容器中。
- @ConditionalOnMissingBean注解
@Configuration
public class HeaderConfig {@Bean@ConditionalOnMissingBean //不存在該類型的bean,才會將該bean加入IOC容器public HeaderParser headerParser(){return new HeaderParser();}//省略其他代碼...
}
執行testHeaderParser()測試方法:
SpringBoot在調用@Bean標識的headerParser()前,IOC容器中是沒有HeaderParser類型的bean,所以HeaderParser對象正常創建,并注冊到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration
public class HeaderConfig {@Bean@ConditionalOnMissingBean(name="deptController2")//不存在指定名稱的bean,才會將該bean加入IOC容器public HeaderParser headerParser(){return new HeaderParser();}//省略其他代碼...
}
因為在SpringBoot環境中不存在名字叫deptController2的bean對象,所以創建HeaderParser對象并注冊到IOC容器中。
再次修改 @ConditionalOnMissingBean
注解:
@Configuration
public class HeaderConfig {@Bean@ConditionalOnMissingBean(HeaderConfig.class)//不存在指定類型的bean,才會將bean加入IOC容器public HeaderParser headerParser(){return new HeaderParser();}//省略其他代碼...
}
@SpringBootTest
public class AutoConfigurationTests {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testHeaderParser(){System.out.println(applicationContext.getBean(HeaderParser.class));}//省略其他代碼...
}
在@ConditionalOnMissingBean注解的后面添加(HeaderConfig.calss),也就是如果不存在HeaderConfig.class類型的bean,才會將下面HeaderParser類型的bean創建出來。
但是我們看到運行測試類的結果報錯,因為HeaderConfig類中添加@Configuration注解,而@Configuration注解中包含了@Component,所以SpringBoot啟動時會創建HeaderConfig類對象,并注冊到 IOC 中。
當IOC容器中有HeaderConfig類型的bean存在時,不會把創建HeaderParser對象注冊到IOC容器中。而IOC容器中沒有HeaderParser類型的對象時,通過getBean(HeaderParser.class)方法獲取bean對象時,引發異常:NoSuchBeanDefinitionException
- @ConditionalOnProperty注解(這個注解和配置文件當中配置的屬性有關系)
先在application.yml配置文件中添加如下的鍵值對:
name: itheima
在聲明bean的時候就可以指定一個條件@ConditionalOnProperty
@Configuration
public class HeaderConfig {@Bean@ConditionalOnProperty(name ="name",havingValue = "itheima")//配置文件中存在指定屬性名與值,才會將bean加入IOC容器public HeaderParser headerParser(){return new HeaderParser();}@Beanpublic HeaderGenerator headerGenerator(){return new HeaderGenerator();}
}
執行testHeaderParser()測試方法:
修改@ConditionalOnProperty注解: havingValue的值修改為"itheima2"
@Bean
@ConditionalOnProperty(name ="name",havingValue = "itheima2")//配置文件中存在指定屬性名與值,才會將bean加入IOC容器
public HeaderParser headerParser(){return new HeaderParser();
}
再次執行testHeaderParser()測試方法:
因為application.yml配置文件中,不存在: name: itheima2,所以HeaderParser對象在IOC容器中不存在
我們再回頭看看之前講解SpringBoot源碼時提到的一個配置類:GsonAutoConfiguration
最后再給大家梳理一下自動配置原理:
自動配置的核心就在@SpringBootApplication注解上,SpringBootApplication這個注解底層包含了3個注解,分別是:
@SpringBootConfiguration
@ComponentScan
@EnableAutoConfiguration
@EnableAutoConfiguration這個注解才是自動配置的核心。
它封裝了一個@Import注解,Import注解里面指定了一個ImportSelector接口的實現類。
在這個實現類中,重寫了ImportSelector接口中的selectImports()方法。
而selectImports()方法中會去讀取兩份配置文件,并將配置文件中定義的配置類做為selectImports()方法的返回值返回,返回值代表的就是需要將哪些類交給Spring的IOC容器進行管理。
那么所有自動配置類的中聲明的bean都會加載到Spring的IOC容器中嗎? 其實并不會,因為這些配置類中在聲明bean時,通常都會添加@Conditional開頭的注解,這個注解就是進行條件裝配。而Spring會根據Conditional注解有選擇性的進行bean的創建。
@Enable 開頭的注解底層,它就封裝了一個注解 import 注解,它里面指定了一個類,是 ImportSelector 接口的實現類。在實現類當中,我們需要去實現 ImportSelector 接口當中的一個方法 selectImports 這個方法。這個方法的返回值代表的就是我需要將哪些類交給 spring 的 IOC容器進行管理。
此時它會去讀取兩份配置文件,一份兒是 spring.factories,另外一份兒是 autoConfiguration.imports。而在 autoConfiguration.imports 這份兒文件當中,它就會去配置大量的自動配置的類。
而前面我們也提到過這些所有的自動配置類當中,所有的 bean都會加載到 spring 的 IOC 容器當中嗎?其實并不會,因為這些配置類當中,在聲明 bean 的時候,通常會加上這么一類@Conditional 開頭的注解。這個注解就是進行條件裝配。所以SpringBoot非常的智能,它會根據 @Conditional 注解來進行條件裝配。只有條件成立,它才會聲明這個bean,才會將這個 bean 交給 IOC 容器管理。
4. Web后端開發總結
到此基于SpringBoot進行web后端開發的相關知識我們已經學習完畢了。下面我們一起針對這段web課程做一個總結。
我們來回顧一下關于web后端開發,我們都學習了哪些內容,以及每一塊知識,具體是屬于哪個框架的。
web后端開發現在基本上都是基于標準的三層架構進行開發的,在三層架構當中,Controller控制器層負責接收請求響應數據,Service業務層負責具體的業務邏輯處理,而Dao數據訪問層也叫持久層,就是用來處理數據訪問操作的,來完成數據庫當中數據的增刪改查操作。
在三層架構中,前端發起請求首先會到達Controller(不進行邏輯處理),然后在Controller會直接調用Service進行邏輯處理,Service再調用Dao完成數據訪問操作。
如果我們在執行具體的業務處理之前,需要去做一些通用的業務處理,比如:我們要進行統一的登錄校驗,我們要進行統一的字符編碼等這些操作時,我們就可以借助于Javaweb當中三大組件之一的過濾器Filter或者是Spring當中提供的攔截器Interceptor來實現。
而為了實現三層架構層與層之間的解耦,我們學習了Spring框架當中的第一大核心:IOC控制反轉與DI依賴注入。
所謂控制反轉,指的是將對象創建的控制權由應用程序自身交給外部容器,這個容器就是我們常說的IOC容器或Spring容器。用的是@Autowired
而DI依賴注入指的是容器為程序提供運行時所需要的資源。
除了IOC與DI我們還講到了AOP面向切面編程,還有Spring中的事務管理、全局異常處理器,以及傳遞會話技術Cookie、Session以及新的會話跟蹤解決方案JWT令牌,阿里云OSS對象存儲服務,以及通過Mybatis持久層架構操作數據庫等技術。
我們在學習這些web后端開發技術的時候,我們都是基于主流的SpringBoot進行整合使用的。而SpringBoot又是用來簡化開發,提高開發效率的。像過濾器、攔截器、IOC、DI、AOP、事務管理等這些技術到底是哪個框架提供的核心功能?
Filter過濾器、Cookie、 Session這些都是傳統的JavaWeb提供的技術。
JWT令牌、阿里云OSS對象存儲服務,是現在企業項目中常見的一些解決方案。
IOC控制反轉、DI依賴注入、AOP面向切面編程、事務管理、全局異常處理、攔截器等,這些技術都是 Spring Framework框架當中提供的核心功能。
Mybatis就是一個持久層的框架,是用來操作數據庫的。
在Spring框架的生態中,對web程序開發提供了很好的支持,如:全局異常處理器、攔截器這些都是Spring框架中web開發模塊所提供的功能,而Spring框架的web開發模塊,我們也稱為:SpringMVC
SpringMVC不是一個單獨的框架,它是Spring框架的一部分,是Spring框架中的web開發模塊,是用來簡化原始的Servlet程序開發的。
外界俗稱的SSM,就是由:SpringMVC、Spring Framework、Mybatis三塊組成。
基于傳統的SSM框架進行整合開發項目會比較繁瑣,而且效率也比較低,所以在現在的企業項目開發當中,基本上都是直接基于SpringBoot整合SSM進行項目開發的。
到此我們web后端開發的內容就已經全部講解結束了。