后端Web實戰-Spring原理

目錄

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原理篇,主要偏向于底層原理。

我們今天的安排包括這么三個部分:

  1. 配置優先級:Springboot項目當中屬性配置的常見方式以及配置的優先級

  2. Bean的管理

  3. 剖析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

配置文件優先級排名(從高到低):

  1. properties配置文件

  2. yml配置文件

  3. yaml配置文件

注意事項:雖然springboot支持多種格式配置文件,但是在項目開發時,推薦統一使用一種格式的配置。(yml是主流)

在SpringBoot項目當中除了以上3種配置文件外,SpringBoot為了增強程序的擴展性,除了支持配置文件的配置方式以外,還支持另外兩種常見的配置方式:

  1. Java系統屬性配置 (格式: -Dkey=value)

-Dserver.port=9000

??????2.命令行參數 (格式:--key=value)

--server.port=10010

那在idea當中運行程序時,如何來指定Java系統屬性和命令行參數呢?

  • 編輯啟動程序的配置信息

重啟服務,同時配置Tomcat端口(三種配置文件、系統屬性、命令行參數),測試哪個Tomcat端口號生效:

刪除命令行參數配置,重啟SpringBoot服務:

優先級: 命令行參數 > 系統屬性參數 > properties參數 > yml參數 > yaml參數

如果項目已經打包上線了,這個時候我們又如何來設置Java系統屬性和命令行參數:

下面我們來演示下打包程序運行時指定Java系統屬性和命令行參數:

  1. 執行maven打包指令package,把項目打成jar文件

  2. 使用命令: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的其他使用細節,主要學習以下三方面:

  1. 如何從IOC容器中手動的獲取到bean對象

  2. bean的作用域配置

  3. 管理第三方的bean對象

接下來我們先來學習第一方面,從IOC容器中獲取bean對象。

2.1 獲取Bean

默認情況下,SpringBoot項目在啟動的時候會自動的創建IOC容器(也稱為Spring容器),并且在啟動的過程當中會自動的將bean對象都創建好,存放在IOC容器當中。應用程序在運行時需要依賴什么bean對象,就直接進行依賴注入就可以了。

而在Spring容器中提供了一些方法,可以主動從IOC容器中獲取到bean對象,下面介紹3種常用方式:

  1. 根據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為例):

    1. 初始化?A:實例化?A(調用無參構造)→ 將?A?的 “工廠” 放入三級緩存 → 準備注入?B(發現?B?未創建);
    2. 初始化?B:實例化?B?→ 將?B?的 “工廠” 放入三級緩存 → 準備注入?A(從三級緩存取出?A?的工廠,生成未完全初始化的?A,放入二級緩存);
    3. B?注入?A(未完全初始化的?A)→?B?初始化完成,放入一級緩存;
    4. 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?構造函數注入?ServiceBServiceB?構造函數注入?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 后的初始化流程(打破循環):

    1. Spring 容器啟動,開始初始化?ServiceA(單例 Bean,默認提前初始化,但因依賴?ServiceB,先處理?ServiceB);
    2. 初始化?ServiceBServiceB?的構造函數需要?ServiceA,但?ServiceA?尚未創建,此時進入循環 ——若沒有 @Lazy,Spring 會直接拋出循環依賴異常
    3. 但?ServiceA?注入?ServiceB?時添加了?@Lazy:Spring 不會立即創建?ServiceB?的真實實例,而是生成一個?ServiceB?的代理對象(基于 JDK 動態代理或 CGLIB,取決于?ServiceB?是否實現接口);
    4. 用?ServiceB?的代理對象注入?ServiceA?的構造函數:ServiceA?拿到代理對象后,成功完成實例化(此時?ServiceA?中的?serviceB?是代理,非真實實例);
    5. ServiceA?實例化完成后,注入?ServiceB?的構造函數:ServiceB?拿到真實的?ServiceA?實例,成功完成實例化;
    6. 后續當代碼第一次調用?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框架進行項目的開發,會比較繁瑣。

    這個繁瑣主要體現在兩個地方:

    1. 在pom.xml中依賴配置比較繁瑣,在項目開發時,需要自己去找到對應的依賴,還需要找到依賴它所配套的依賴以及對應版本,否則就會出現版本沖突問題。

    2. 在使用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多要掃描的包,這種方式會很繁瑣。而且這種大面積的掃描性能也比較低。

    缺點:

    1. 使用繁瑣

    2. 性能低

    結論:SpringBoot中并沒有采用以上這種方案。

    3.2.2.3 方案二

    @Import導入

    • 導入形式主要有以下幾種:

      1. 導入普通類

      2. 導入配置類

      3. 導入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后端開發的內容就已經全部講解結束了。

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/bicheng/98098.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/bicheng/98098.shtml
    英文地址,請注明出處:http://en.pswp.cn/bicheng/98098.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    在 Qoder 等 AI 二創 IDE 里用 VS Code Remote-SSH 的“曲線連接”實戰

    目標&#xff1a;讓你在 Qoder 等在線/AI 輔助 IDE 中&#xff0c;也能像本地 VS Code 一樣通過 Remote-SSH 連接到自己的遠程服務器進行開發。 前提&#xff1a;只在你擁有或被授權的服務器上使用&#xff0c;遵守所用平臺的條款與限制。兩句話說清楚 先用本地 VS Code 正常連…

    python發送請求SSL驗證設置

    這個錯誤通常是由于SSL/TLS握手失敗導致的&#xff0c;可能原因包括證書驗證問題、不兼容的加密協議或網絡連接中斷。以下是幾種解決方案&#xff0c;按推薦順序排列&#xff1a; 方案一&#xff1a;臨時禁用SSL驗證&#xff08;快速測試&#xff09; response requests.get(u…

    工廠自動化正從 “人工堆疊” 向 “設備替代” 快速轉變

    ?人工進行零件排列&#xff0c;雖在操作靈活性上有一定表現&#xff0c;但實際應用中存在明顯短板&#xff0c;對工廠自動化轉型形成制約。從成本來看&#xff0c;一名工人日均工資約數百元&#xff0c;若需 5-6 名工人協同作業&#xff0c;月均人力成本易突破萬元&#xff0c…

    中標麒麟7.4部署gitlab-runner

    1. 部署環境 本次部署環境完全斷網。需要離線下載gitlab-runner及其依賴。 本次部署環境為中標麒麟7.4。目前機器上部署了gitlab&#xff0c;安裝了maven。 2. 部署步驟 2.1 在外部下載好依賴 我首先在騰訊云上布置了一個centos7.9的虛擬機&#xff0c;沒有安裝任何東西。 …

    在 IDEA 2024 創建 Vue 項目(保姆級)

    目錄 一、 前后端分離 1. 簡介 2. 實現前后端分離的常用前端框架 3. 前后端分離和動靜分離 3.1 前后端分離: 3.2 動靜分離: 二、 Vue.js概述 1. 簡介 2. SPA介紹 2.1 優點 2.2 缺點 3. MVVM介紹 3.1 示例 三、 名詞解釋 1. Node.js 2. npm 3. webpack 4. Vue…

    Coze源碼分析-資源庫-創建知識庫-后端源碼-應用/領域/數據訪問

    3. 應用服務層 3.1 知識庫應用服務 文件位置: backend/application/knowledge/knowledge.go func (k *KnowledgeApplicationService) CreateKnowledge(ctx context.Context, req *dataset.CreateDatasetRequest) (*dataset.CreateDatasetResponse, error) {// 1. 轉換文檔類型d…

    Shopify指紋手機矩陣:無限擴店,橫掃FB/GG廣告封號風險

    一、 為什么需要為Shopify使用指紋手機&#xff1f;雖然Shopify不會因為你多開店而封號&#xff0c;但以下場景需要隔離環境&#xff1a;規避廣告平臺關聯&#xff1a;這是最核心的用途。你會用Facebook、Google、TikTok等廣告平臺為你的Shopify店鋪引流。這些廣告平臺嚴格禁止…

    【Python】家庭用電數據分析Prophet預測

    數據集&#xff1a;Household Electricity Consumption | Kaggle 目錄 數據集簡介 探索性分析 Prophet預測 Prophet模型 Prophet理念 Prophet優點 數據集簡介 240000-household-electricity-consumption-records數據集包含了一個家庭6個月的用電數據&#xff0c;收集于2…

    信息系統運維管理

    運行維護服務指的是采用信息技術手段及方法&#xff0c;依據客戶提出的服務要求&#xff0c;為其在使用信息系統過程中提出的需求提供的綜合服務是信息技術服務中的一種主要類型。運行維護服務對象是指信息系統工程建設項目交付的內容&#xff0c;包括機房基礎設施&#xff0c;…

    系統編程完結整理以及補充

    Shell&#xff08;命令與腳本語法&#xff09; 系統編程&#xff08;一&#xff09;shell的學習-CSDN博客 功能/概念語法/關鍵字參數/用法說明返回值/效果難易點注意事項示例/實驗提示定義函數func_name() { commands; }無參數或通過 $1 $2 ... 傳參函數執行參數傳遞、全局變…

    第十四屆藍橋杯青少組C++選拔賽[2022.12.18]第二部分編程題(2、字符翻轉)

    參考程序&#xff1a;#include <bits/stdc.h> using namespace std;int main() {string s;cin >> s; // 讀取輸入字符串&#xff0c;若無輸入則結束for (int i 0; i < (int)s.size(); i) {// i 從 0 開始&#xff0c;位置是 i1&#xff1b;如果 i 是奇數&#…

    Django基礎環境入門

    熟悉過程 搭建環境&#xff0c;運行起來基礎請求到服務接口跟java web對比 說明先不糾結細節先跑起來再說 1. 環境搭建 python已經安裝&#xff0c;使用conda管理 django安裝 django官方文檔 pip install django也可以命令創建 mkdir djangotutorial django-admin startp…

    408學習之c語言(結構體)

    今天給大家分享C語言中結構體的幾種常見使用方法&#xff0c;包括基礎結構體定義與初始化&#xff0c;結構體指針的兩種訪問方式&#xff0c;結構體數組的遍歷&#xff0c;動態內存分配與結構體使用&#xff0c;typedef簡化結構體類型基礎結構體定義與使用#define _CRT_SECURE_…

    Navicat中設計表格默認值時,如何不設置成NULL,而是設置成空文本?

    在 Navicat 中設計表時&#xff0c;將字段的默認值設置為空文本而不是 NULL 是一個非常常見的需求。操作很簡單&#xff0c;但有幾個細節需要注意。■ 方法一&#xff1a;通過“設計表”界面設置&#xff08;最常用&#xff09;1. 連接數據庫并找到表&#xff1a;在左側連接導…

    深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)第十三章知識點問答(15題)

    預告下一本 可能是mysql8的書籍 或者是AI應用工程的基本崗位所有技能 問題1 什么是 線程安全&#xff1f;在 Java 中如何定義“線程安全”&#xff1f;線程安全&#xff08;Thread Safety&#xff09; 的定義是&#xff1a; 當多個線程同時訪問某個類的對象時&#xff0c;無論運…

    【醫療 AI】Baichuan-M2:大語言模型在醫療領域的動態驗證框架

    Baichuan-M2 醫療大模型&#xff1a;技術解讀與使用方法 Baichuan-M2&#xff1a;大語言模型在醫療領域的動態驗證框架 【醫療 AI】Baichuan-M2&#xff1a;大語言模型在醫療領域的動態驗證框架0. Baichuan-M2 模型簡介0.1 基本信息0.2 主要貢獻0.3 論文摘要1. 引言2. 驗證系統…

    Ubuntu\Linux環境中驅動版本配置cudaToolKit

    修改環境變量。 1. 首先檢查當前的環境變量 # 查看當前PATH echo $PATH# 查看當前LD_LIBRARY_PATH echo $LD_LIBRARY_PATH# 查看當前CUDA_HOME echo $CUDA_HOME2. 確定正確的CUDA安裝路徑 # 查看系統中有哪些CUDA版本 ls /usr/local/cuda*3. 修改環境變量(永久生效) 編輯…

    Linux基礎開發工具(gcc/g++,yum,vim,make/makefile)

    目錄 軟件包管理器——yum Linux下&#xff0c;軟件的安裝 yum與軟件包的關系 yum命令的運用 1.查看軟件包 2.安裝/刪除軟件包 編輯器——vim vim的基本概念 vim的基本操作 命令模式命令 移動光標 刪除文字 撤銷上一次操作 跳至指定的行 底行模式命令 編譯器——…

    數據結構之跳表

    跳表&#xff08;Skip List&#xff09;是一種基于概率平衡的數據結構&#xff0c;通過多層有序鏈表實現高效的查找、插入和刪除操作。它在最壞情況下時間復雜度為 (O(n))&#xff0c;但通過隨機化設計&#xff0c;平均時間復雜度可優化至 (O(\log n))&#xff0c;與平衡二叉搜…

    線程概念,控制

    一、線程概念 線程概念&#xff1a;進程內部的一個執行流&#xff0c;輕量化。 觀點&#xff1a;進程是系統分配資源的基本單位&#xff0c;線程是CPU調度的基本單位。 在理解線程之前&#xff0c;我們在談一下虛擬地址空間。 我們都知道進程是通過頁表將虛擬地址轉化為物理地址…