前言
????在前面的所有章節(jié)當中,我們學習的都是web開發(fā)的技術使用,都是面向應用層面的,我們學會了怎么樣去用。而我們今天所要學習的是web后端開發(fā)
的最后一個篇章springboot原理篇,主要偏向于底層原理。
本章節(jié)的安排包括三個部分:
????1. 配置優(yōu)先級:Springboot項目當中屬性配置的常見方式以及配置的優(yōu)先級
????2. Bean的管理
????3. 剖析Springboot的底層原理
1. 配置優(yōu)先級
在我們前面的章節(jié)當中,我們已經講解了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
三種配置文件,優(yōu)先級最高的是properties
????? yaml
、yml
兩種配置文件同時存在
配置文件優(yōu)先級排名(從高到低):
????1.properties
配置文件
????2.yml
配置文件
????3.yaml
配置文件
注意事項:雖然springboot支持多種格式配置文件,但是在項目開發(fā)時,推薦統(tǒng)一使用一種格式的配置。(yml是主流)
????
????在SpringBoot項目當中除了以上3種配置文件外,SpringBoot為了增強程序的擴展性,除了支持配置文件的配置方式以外,還支持另外兩種常見的配置方式:
-
Java系統(tǒng)屬性
配置 (格式: -Dkey=value)
-Dserver.port=9000
-
命令行參數
(格式:–key=value)
--server.port=10010
????
那在idea當中運行程序時,如何來指定Java系統(tǒng)屬性
和命令行參數
呢?
????? 編輯啟動程序的配置信息
重啟服務,同時配置Tomcat端口(三種配置文件
、系統(tǒng)屬性
、命令行參數
),測試哪個Tomcat端口號生效:
刪除命令行參數配置,重啟SpringBoot服務:
優(yōu)先級:
命令行參數
>系統(tǒng)屬性參數
>properties參數
>yml參數
>yaml參數
思考:如果項目已經打包上線了,這個時候我們又如何來設置Java系統(tǒng)屬性和命令行參數呢?
java -Dserver.port=9000 -jar XXXXX.jar --server.port=10010
下面我們來演示下打包程序運行時指定Java系統(tǒng)屬性和命令行參數:
????1. 執(zhí)行maven打包指令package,把項目打成jar文件
????2. 使用命令:java -jar 方式運行jar文件程序
項目打包:
運行jar程序:
????? 同時設置Java系統(tǒng)屬性和命令行參數
java -Dserver.port=9000 -jar springboot-web-config-0.0.1-SNAPSHOT.jar --server.port=10010
????? 僅設置Java系統(tǒng)屬性
注意事項:
????? Springboot項目進行打包時,需要引入插件 spring-boot-maven-plugin (基于官網骨架創(chuàng)建項目,會自動添加該插件)
????在SpringBoot項目當中,常見的屬性配置方式有5種, 3種配置文件,加上2種外部屬性的配置(Java系統(tǒng)屬性、命令行參數)。通過以上的測試,我們也得出了優(yōu)先級(從低到高):
????? application.yaml
(忽略)
????? application.yml
????? application.properties
????? java系統(tǒng)屬性
(-Dxxx=xxx
)
????? 命令行參數
(--xxx=xxx
)
2. Bean管理
????在前面的章節(jié)當中,已經講過了我們可以通過Spring當中提供的注解@Component
以及它的三個衍生注解(@Controller
、@Service
、@Repository
)來聲明IOC容器
中的bean對象
,同時我們也學習了如何為應用程序注入運行時所需要依賴的bean對象,也就是依賴注入DI
。
我們本章主要學習IOC容器
中Bean
的其他使用細節(jié),主要學習以下三方面:
????1. 如何從IOC容器中手動的獲取到bean對象
????2. bean的作用域配置
????3. 管理第三方的bean對象
接下來我們先來學習第一方面,從IOC容器
中獲取bean對象
。
2.1. 獲取Bean
????默認情況下,SpringBoot項目在啟動的時候會自動的創(chuàng)建IOC容器(也稱為Spring容器),并且在啟動的過程當中會自動的將bean對象都創(chuàng)建好,存放在IOC容器當中。應用程序在運行時需要依賴什么bean對象,就直接進行依賴注入就可以了。
而在Spring容器中提供了一些方法,可以主動從IOC容器中獲取到bean對象,下面介紹3種常用方式:
- 根據name獲取bean
Object getBean(String name)
- 根據類型獲取bean
<T> T getBean(Class<T> requiredType)
- 根據name獲取bean(帶類型轉換)
<T> T getBean(String name, Class<T> requiredType)
思考:要從IOC容器當中來獲取到bean對象,需要先拿到IOC容器對象,怎么樣才能拿到IOC容器呢?
解答:想獲取到IOC容器,直接將IOC容器對象注入進來就可以了
控制器:DeptController.java
:
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
@GetMapping
public 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();
}
@PostMapping
public Result save(@RequestBody Dept dept){
deptService.save(dept);
return Result.success();
}
}
業(yè)務實現類:DeptServiceImpl.java
:
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> list() {
List<Dept> deptList = deptMapper.list();
return deptList;
}
@Override
public void delete(Integer id) {
deptMapper.delete(id);
}
@Override
public void save(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.save(dept);
}
}
Mapper接口DeptMapper.java
:
@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);
}
test文件夾中的測試類SpringbootWebConfig2ApplicationTests.java
:
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private ApplicationContext applicationContext; //IOC容器對象
//獲取bean對象
@Test
public void testGetBean(){
//根據bean的名稱獲取
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作用域)中講解
注意事項:
????上述所說的【Spring項目啟動時,會把其中的bean都創(chuàng)建好】還會受到作用域及延遲初始化影響,這里主要針對于默認的單例非延遲加載的bean而言。
2.2. Bean作用域
????在前面我們提到的IOC容器當中,默認bean對象是單例模式(只有一個實例對象)。那么如何設置bean對象為非單例呢?需要設置bean的作用域。
在Spring中支持五種作用域,后三種在web環(huán)境才生效:
作用域 | 說明 |
---|---|
singleton | 容器內同名稱的bean只有一個實例(單例)(默認) |
prototype | 每次使用該bean時會創(chuàng)建新的實例(非單例) |
request | 每個請求范圍內會創(chuàng)建新的實例(web環(huán)境中,了解) |
session | 每個會話范圍內會創(chuàng)建新的實例(web環(huán)境中,了解) |
session | 每個應用范圍內會創(chuàng)建新的實例(web環(huán)境中,了解) |
知道了bean的5種作用域了,我們要怎么去設置一個bean的作用域呢?
可以借助Spring中的@Scope
注解來進行配置作用域
????
1). 測試一
????? 控制器
//默認bean的作用域為:singleton (單例)
@Lazy //延遲加載(第一次使用bean對象時,才會創(chuàng)建bean對象并交給ioc容器管理)
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
//省略其他代碼...
}
????? 測試類
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private ApplicationContext applicationContext; //IOC容器對象
//bean的作用域
@Test
public 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,在容器啟動時被創(chuàng)建,可以使用@Lazy
注解來延遲初始化(延遲到第一次使用時)
????
2). 測試二
????修改控制器DeptController代碼:
@Scope("prototype") //bean作用域為非單例
@Lazy //延遲加載
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
//省略其他代碼...
}
重啟SpringBoot服務,再次執(zhí)行測試方法,查看控制吧打印的日志:
注意事項:
?????prototype
的bean,每一次使用該bean的時候都會創(chuàng)建一個新的實例
????? 實際開發(fā)當中,絕大部分的Bean是單例的,也就是說絕大部分Bean不需要配置scope屬性
2.3. 第三方Bean
????學習完bean的獲取、bean的作用域之后,接下來我們再來學習第三方bean的配置。
????之前我們所配置的bean,像controller、service,dao三層體系下編寫的類,這些類都是我們在項目當中自己定義的類(自定義類)。當我們要聲明這些bean,也非常簡單,我們只需要在類上加上@Component以及它的這三個衍生注解(@Controller、@Service、@Repository),就可以來聲明這個bean對象了。
????但是在我們項目開發(fā)當中,還有一種情況就是這個類它不是我們自己編寫的,而是我們引入的第三方依賴當中提供的。
在pom.xml
文件中,引入dom4j
:
<!--Dom4j-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
dom4j
就是第三方組織提供的。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容器bean
public SAXReader saxReader(){
return new SAXReader();
}
}
xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<emp>
<name>Tom</name>
<age>18</age>
</emp>
測試類:
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private SAXReader saxReader;
//第三方bean的管理
@Test
public 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服務,執(zhí)行測試方法后,控制臺輸出日志:Tom : 18
說明:以上在啟動類中聲明第三方Bean的作法,不建議使用(項目中要保證啟動類的純粹性)
解決方案2:在配置類
中定義@Bean標識的方法
如果需要定義第三方Bean時, 通常會單獨定義一個配置類
在com.itheima包
中創(chuàng)建config包
,然后在config包
中創(chuàng)建CommonConfig.java
:
@Configuration //配置類 (在配置類當中對第三方bean進行集中的配置管理)
public class CommonConfig {
//聲明第三方bean
@Bean //將當前方法的返回值對象交給IOC容器管理, 成為IOC容器bean
//通過@Bean注解的name/value屬性指定bean名稱, 如果未指定, 默認是方法名
public SAXReader reader(DeptService deptService){
System.out.println(deptService);
return new SAXReader();
}
}
注釋掉SpringBoot啟動類中創(chuàng)建第三方bean對象的代碼
重啟服務,執(zhí)行測試方法,查看控制臺日志:Tom : 18
????在方法上加上一個@Bean注解
,Spring 容器在啟動的時候,它會自動的調用這個方法,并將方法的返回值聲明為Spring容器當中的Bean對象。
注意事項 :
????? 通過@Bean注解
的name
或value
屬性可以聲明bean的名稱
,如果不指定,默認bean的名稱
就是方法名
。
????? 如果第三方bean需要依賴其它bean對象,直接在bean定義方法中設置形參即可,容器會根據類型自動裝配
關于Bean大家只需要保持一個原則:
????? 如果是在項目當中我們自己定義的類,想將這些類交給IOC容器管理,我們直接使用@Component以及它的衍生注解來聲明就可以。
????? 如果這個類它不是我們自己定義的,而是引入的第三方依賴當中提供的類,而且我們還想將這個類交給IOC容器管理。此時我們就需要在配置類中定義一個方法,在方法上加上一個@Bean注解,通過這種方式來聲明第三方的bean對象。
3. SpringBoot原理
????經過前面章節(jié)的學習,大家也會發(fā)現基于SpringBoot進行web程序的開發(fā)是非常簡單、非常高效的。
????SpringBoot使我們能夠集中精力地去關注業(yè)務功能的開發(fā),而不用過多地關注框架本身的配置使用。而我們前面所講解的都是面向應用層面的技術,接下來我們開始學習SpringBoot的原理,這部分內容偏向于底層的原理分析。
????在剖析SpringBoot的原理之前,我們先來快速回顧一下我們前面所講解的Spring家族的框架。
????Spring是目前世界上最流行的Java框架,它可以幫助我們更加快速、更加容易的來構建Java項目。而在Spring家族當中提供了很多優(yōu)秀的框架,而所有的框架都是基于一個基礎框架的SpringFramework
(也就是Spring框架)。而前面我們也提到,如果我們直接基于Spring框架進行項目的開發(fā),會比較繁瑣。
這個繁瑣主要體現在兩個地方:
????1. 在pom.xml中依賴配置比較繁瑣,在項目開發(fā)時,需要自己去找到對應的依賴,還需要找到依賴它所配套的依賴以及對應版本,否則就會出現版本沖突問題。
????2. 在使用Spring框架進行項目開發(fā)時,需要在Spring的配置文件中做大量的配置,這就造成Spring框架入門難度較大,學習成本較高。
????基于Spring存在的問題,官方在Spring框架4.0版本之后,又推出了一個全新的框架:SpringBoot。
????通過 SpringBoot來簡化Spring框架的開發(fā)(是簡化不是替代)。我們直接基于SpringBoot來構建Java項目,會讓我們的項目開發(fā)更加簡單,更加快捷。
????SpringBoot框架之所以使用起來更簡單更快捷,是因為SpringBoot框架底層提供了兩個非常重要的功能:一個是起步依賴,一個是自動配置。
????通過SpringBoot所提供的起步依賴,就可以大大的簡化pom文件當中依賴的配置,從而解決了Spring框架當中依賴配置繁瑣的問題。
????
????通過自動配置的功能就可以大大的簡化框架在使用時bean的聲明以及bean的配置。我們只需要引入程序開發(fā)時所需要的起步依賴,項目開發(fā)時所用到常見的配置都已經有了,我們直接使用就可以了
????簡單回顧之后,接下來我們來學習下SpringBoot的原理。其實學習SpringBoot的原理就是來解析SpringBoot當中的起步依賴
與自動配置
的原理。我們首先來學習SpringBoot當中起步依賴的原理
3.1. 起步依賴
????假如我們沒有使用SpringBoot,用的是Spring框架進行web程序的開發(fā),此時我們就需要引入web程序開發(fā)所需要的一些依賴。
spring-webmvc
依賴:這是Spring框架進行web程序開發(fā)所需要的依賴
????servlet-api
依賴:Servlet基礎依賴
????jackson-databind
依賴:JSON處理工具包
????
如果要使用AOP
,還需要引入aop
依賴、aspect
依賴
????
項目中所引入的這些依賴,還需要保證版本匹配,否則就可能會出現版本沖突問題。
????如果我們使用了SpringBoot,就不需要像上面這么繁瑣的引入依賴了。我們只需要引入一個依賴就可以了,那就是web開發(fā)的起步依賴:springboot-starter-web
。
為什么我們只需要引入一個web開發(fā)的起步依賴,web開發(fā)所需要的所有的依賴都有了呢?
????因為Maven的依賴傳遞。
????? 在SpringBoot給我們提供的這些起步依賴當中,已提供了當前程序開發(fā)所需要的所有的常見依賴(官網地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters )。
????? 比如:springboot-starter-web
,這是web開發(fā)的起步依賴,在web開發(fā)的起步依賴當中,就集成了web開發(fā)中常見的依賴:json
、web
、webmvc
、tomcat
等。我們只需要引入這一個起步依賴,其他的依賴都會自動的通過Maven的依賴傳遞進來。
結論:起步依賴的原理就是Maven的依賴傳遞
。
3.2. 自動配置
????我們講解了SpringBoot當中起步依賴的原理,就是Maven的依賴傳遞。接下來我們解析下自動配置的原理,我們要分析自動配置的原理,首先要知道什么是自動配置。
3.2.1. 概述
????SpringBoot的自動配置就是當Spring容器啟動后,一些配置類、bean對象就自動存入到了IOC容器中,不需要我們手動去聲明,從而簡化了開發(fā),省去了繁瑣的配置操作。
比如:我們要進行事務管理、要進行AOP程序的開發(fā),此時就不需要我們再去手動的聲明這些bean對象了,我們直接使用就可以從而大大的簡化程序的開發(fā),省去了繁瑣的配置操作。
????
下面我們打開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
就自動注入了:
import com.google.gson.Gson;
import com.itheima.pojo.Result;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private Gson gson;
@Test
public void testJson(){
String json = gson.toJson(Result.success());
System.out.println(json);
}
}
添加斷點,使用debug模式運行測試類程序:
問題:在當前項目中我們并沒有聲明谷歌提供的Gson這么一個bean對象,然后我們卻可以通過@Autowired從Spring容器中注入bean對象,那么這個bean對象怎么來的?
答案:SpringBoot項目在啟動時通過自動配置完成了bean對象的創(chuàng)建。
????體驗了SpringBoot的自動配置了,下面我們就來分析自動配置的原理。其實分析自動配置原理就是來解析在SpringBoot項目中,在引入依賴之后是如何將依賴jar包當中所定義的配置類以及bean加載到SpringIOC容器中的。
3.2.2. 常見方案
概述
????我們知道了什么是自動配置之后,接下來我們就要來剖析自動配置的原理。解析自動配置的原理就是分析在 SpringBoot項目當中,我們引入對應的依賴之后,是如何將依賴jar包當中所提供的bean以及配置類直接加載到當前項目的SpringIOC容器當中的。
接下來,我們就直接通過代碼來分析自動配置原理。
準備工作:在Idea中導入"資料\03. 自動配置原理"下的 itheima-utils工程
????
????1、在SpringBoot項目 spring-boot-web-config2 工程中,通過坐標引入itheima-utils依賴
itheima-utils的項目結構如下:
其中,TokenParser.java
:
@Component
public class TokenParser {
public void parse(){
System.out.println("TokenParser ... parse ...");
}
}
????2、在測試類中,添加測試方法
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testTokenParse(){
System.out.println(applicationContext.getBean(TokenParser.class));
}
//省略其他代碼...
}
????3、執(zhí)行測試方法
異常信息描述: 沒有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容器中)
方案一
@ComponentScan
組件掃描
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要掃描的包
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
重新執(zhí)行測試方法,控制臺日志輸出:
????大家可以想象一下,如果采用以上這種方式來完成自動配置,那我們進行項目開發(fā)時,當需要引入大量的第三方的依賴,就需要在啟動類上配置N多要掃描的包,這種方式會很繁瑣。而且這種大面積的掃描性能也比較低。
????
缺點:
????1.使用繁瑣
????2.性能低
結論:SpringBoot中并沒有采用以上這種方案。
方案二
@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);
}
}
重新執(zhí)行測試方法,控制臺日志輸出:
2). 使用@Import
導入配置類:
????? 配置類
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public 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 {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
@Test
public void testHeaderGenerator(){
System.out.println(applicationContext.getBean(HeaderGenerator.class
));
}
//省略其他代碼...
}
執(zhí)行測試方法:
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);
}
}
執(zhí)行測試方法:
我們使用@Import注解
通過這三種方式都可以導入第三方依賴中所提供的bean
或者是配置類
。
思考:如果基于以上方式完成自動配置,當要引入一個第三方依賴時,是不是還要知道第三方依賴中有哪些配置類和哪些Bean對象?
答案:是的。(對程序員來講,很不友好,而且比較繁瑣)
????
思考:當我們要使用第三方依賴,依賴中到底有哪些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);
}
}
執(zhí)行測試方法:
以上四種方式都可以完成導入操作,但是 第4種 方式會更方便更優(yōu)雅,而這種方式也是SpringBoot當中所采用的方式。
3.2.3. 原理分析
3.2.3.1. 源碼跟蹤
????前面我們講解了在項目當中引入第三方依賴之后,如何加載第三方依賴中定義好的bean對象以及配置類,從而完成自動配置操作。那下面我們通過源碼跟蹤的形式來剖析下SpringBoot底層到底是如何完成自動配置的。
源碼跟蹤技巧:
????在跟蹤框架源碼的時候,一定要抓住關鍵點,找到核心流程。一定不要從頭到尾一行代碼去看,一個方法的去研究,一定要找到關鍵流程,抓住關鍵點,先在宏觀上對整個流程或者整個原理有一個認識,有精力再去研究其中的細節(jié)。
????
要搞清楚SpringBoot的自動配置原理,要從SpringBoot啟動類上使用的核心注解@SpringBootApplication
開始分析:
????
在@SpringBootApplication注解中包含了:
????? 元注解
(不再解釋)
????? @SpringBootConfiguration
????? @EnableAutoConfiguration
????? @ComponentScan
(1)我們先來看第一個注解:@SpringBootConfiguration
@SpringBootConfiguration注解
上使用了@Configuration
,表明SpringBoot啟動類就是一個配置類。
????@Indexed注解
,是用來加速應用啟動的(不用關心)。
(2)接下來再先看@ComponentScan注解
:
@ComponentScan注解
是用來進行組件掃描的,掃描啟動類所在的包及其子包下所有被@Component
及其衍生注解
聲明的類。
????
SpringBoot啟動類,之所以具備掃描包功能,就是因為包含了@ComponentScan注解
。
(3)最后我們來看看@EnableAutoConfiguration注解
(自動配置核心注解):
使用
@Import注解
,導入了實現ImportSelector接口的實現類
。AutoConfigurationImportSelector類
是ImportSelector接口的實現類
。
AutoConfigurationImportSelector類
中重寫了ImportSelector接口
的selectImports()方法
:
selectImports()方法
底層調用getAutoConfigurationEntry()方法
,獲取可自動配置的配置類信息集合
getAutoConfigurationEntry()方法
通過調用getCandidateConfigurations(annotationMetadata, attributes)方法
獲取在配置文件中配置的所有自動配置類的集合
getCandidateConfigurations方法
的功能:
????獲取所有基于METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
、META-INF/spring.factories文件
中配置類的集合
????
????METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
和META-INF/spring.factories文件
這兩個文件在哪里呢?
????通常在引入的起步依賴中,都有包含以上兩個文件
????在前面在給大家演示自動配置的時候,我們直接在測試類當中注入了一個叫gson
的 bean對象,進行JSON格式轉換。雖然我們沒有配置bean對象,但是我們是可以直接注入使用的。原因就是因為在自動配置類當中做了自動配置。到底是在哪個自動配置類當中做的自動配置呢?我們通過搜索來查詢一下。
????在METAINF/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包中METAINF/spring.factories
、METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
兩個文件里面定義的配置類(配置類中定義了@Bean注解標
識的方法)。
????
????當SpringBoot程序啟動時,就會加載配置文件當中所定義的配置類,并將這些配置類信息(類的全限定名)封裝到String類型的數組中,最終通過@Import注解
將這些配置類全部加載到Spring的IOC容器
中,交給IOC容器
管理。
????最后呢,給大家拋出一個問題:在
METAINF/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
:判斷環(huán)境中有對應字節(jié)碼文件,才注冊bean到IOC容器。
????????@ConditionalOnMissingBean
:判斷環(huán)境中沒有對應的bean(類型或名稱),才注冊bean到IOC容器。
????????@ConditionalOnProperty
:判斷配置文件中有對應屬性和值,才注冊bean到IOC容器。
下面我們通過代碼來演示下Conditional注解
的使用:
????? @ConditionalOnClass注解
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnClass(name="io.jsonwebtoken.Jwts")//環(huán)境中存在指定的這個類,才會將該bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代碼...
}
????? pom.xml
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
????? 測試類
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
//省略其他代碼...
}
執(zhí)行testHeaderParser()測試方法:
因為io.jsonwebtoken.Jwts字節(jié)碼文件在啟動SpringBoot程序時已存在,所以創(chuàng)建HeaderParser對象并注冊到IOC容器中。
????? @ConditionalOnMissingBean注解
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean //不存在該類型的bean,才會將該bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代碼...
}
執(zhí)行testHeaderParser()測試方法:
????SpringBoot在調用@Bean標識的headerParser()前,IOC容器中是沒有HeaderParser類型的bean,所以HeaderParser對象正常創(chuàng)建,并注冊到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(name="deptController2")//不存在指定名稱的bean,才會將該bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代碼...
}
執(zhí)行testHeaderParser()測試方法:
因為在SpringBoot環(huán)境中不存在名字叫deptController2的bean對象,所以創(chuàng)建HeaderParser對象并注冊到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(HeaderConfig.class)//不存在指定類型的bean,才會將bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代碼...
}
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
//省略其他代碼...
}
執(zhí)行testHeaderParser()測試方法:
????
????因為HeaderConfig類
中添加@Configuration注解
,而@Configuration注解
中包含了@Component
,所以SpringBoot啟動時會創(chuàng)建HeaderConfig類對象
,并注冊到IOC容器
中。
????
????當IOC容器
中有HeaderConfig
類型的bean存在時,不會把創(chuàng)建HeaderParser對象
注冊到IOC容器
中。而IOC容器
中沒有HeaderParser
類型的對象時,通過getBean(HeaderParser.class)方法
獲取bean對象時,引發(fā)異常: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();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
執(zhí)行testHeaderParser()測試方法:
修改@ConditionalOnProperty注解
: havingValue的值修改為"itheima2"
@Bean
@ConditionalOnProperty(name ="name",havingValue = "itheima2")//配置文件中存在指定屬性名與值,才會將bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
再次執(zhí)行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
的創(chuàng)建。
????
?????@Enable
開頭的注解底層,它就封裝了一個注解import 注解
,它里面指定了一個類,是ImportSelector 接口
的實現類。在實現類當中,我們需要去實現ImportSelector 接口
當中的一個方法selectImports
這個方法。這個方法的返回值代表的就是我需要將哪些類交給 spring 的IOC容器
進行管理。
????
????? 此時它會去讀取兩份配置文件,一份兒是spring.factories
,另外一份兒是autoConfiguration.imports
。而在autoConfiguration.imports
這份兒文件當中,它就會去配置大量的自動配置的類。
????
????? 而前面我們也提到過這些所有的自動配置類當中,所有的bean
都會加載到 spring 的IOC容器
當中嗎?其實并不會,因為這些配置類當中,在聲明bean
的時候,通常會加上這么一類@Conditional
開頭的注解。這個注解就是進行條件裝配。所以SpringBoot非常的智能,它會根據@Conditional 注解
來進行條件裝配。只有條件成立,它才會聲明這個bean
,才會將這個bean
交給IOC 容器
管理。
3.2.4. 案例
3.2.4.1. 自定義starter分析
????前面我們解析了SpringBoot中自動配置的原理,下面我們就通過一個自定義starter案例來加深大家對于自動配置原理的理解。首先介紹一下自定義starter的業(yè)務場景,再來分析一下具體的操作步驟。
????所謂starter
指的就是SpringBoot當中的起步依賴。在SpringBoot當中已經給我們提供了很多的起步依賴了,我們?yōu)槭裁催€需要自定義 starter 起步依賴?
問題:為什么還需要自定義 starter 起步依賴?
????
解答:
????這是因為在實際的項目開發(fā)當中,我們可能會用到很多第三方的技術,并不是所有的第三方的技術官方都給我們提供了與SpringBoot整合的starter起步依賴,但是這些技術又非常的通用,在很多項目組當中都在使用。
業(yè)務場景:
????? 我們前面案例當中所使用的阿里云OSS對象存儲服務,現在阿里云的官方是沒有給我們提供對應的起步依賴的,這個時候使用起來就會比較繁瑣,我們需要引入對應的依賴。我們還需要在配置文件當中進行配置,還需要基于官方SDK示例來改造對應的工具類,我們在項目當中才可以進行使用。
????? 大家想在我們當前項目當中使用了阿里云OSS,我們需要進行這么多步的操作。在別的項目組當中要想使用阿里云OSS,是不是也需要進行這么多步的操作,所以這個時候我們就可以自定義一些公共組件
,在這些公共組件當中,我就可以提前把需要配置的bean都提前配置好。將來在項目當中,我要想使用這個技術,我直接將組件對應的坐標直接引入進來,就已經自動配置好了,就可以直接使用了。我們也可以把公共組件提供給別的項目組進行使用,這樣就可以大大的簡化我們的開發(fā)。
在SpringBoot項目中,一般都會將這些公共組件
封裝為SpringBoot當中的starter
,也就是我們所說的起步依賴
。
SpringBoot官方starter命名:
spring-boot-starter-xxxx
第三組織提供的starter命名:xxxx-spring-boot-starter
????Mybatis提供了配置類,并且也提供了springboot會自動讀取的配置文件。當SpringBoot項目啟動時,會讀取到spring.factories配置文件中的配置類并加載配置類,生成相關bean對象注冊到IOC容器中。
????結果:我們可以直接在SpringBoot程序中使用Mybatis自動配置的bean對象
在自定義一個起步依賴starter
的時候,按照規(guī)范需要定義兩個模塊:
????1. starter模塊
(進行依賴管理 [把程序開發(fā)所需要的依賴都定義在starter起步依賴中] )
????2. autoconfigure模塊
(自動配置)
將來在項目當中進行相關功能開發(fā)時,只需要引入一個起步依賴就可以了,因為它會將
autoconfigure自動配置
的依賴給傳遞下來。
????上面我們簡單介紹了自定義starter的場景,以及自定義starter時涉及到的模塊之后,接下來我們就來完成一個自定義starter的案例。
????
需求:
????自定義aliyun-oss-spring-boot-starter
,完成阿里云OSS操作工具類AliyunOSSUtils
的自動配置。
目標:
????引入起步依賴引入之后,要想使用阿里云OSS,注入AliyunOSSUtils直接使用即可。
????
之前阿里云OSS的使用:
????? 配置文件
#配置阿里云OSS參數
aliyun:
oss:
endpoint: https://oss-cn-shanghai.aliyuncs.com
accessKeyId: LTAI5t9MZK8iq5T2Av5GLDxX
accessKeySecret: C0IrHzKZGKqU8S7YQcevcotD3Zd5Tc
bucketName: web-framework01
????? AliOSSProperties類
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
//區(qū)域
private String endpoint;
//身份ID
private String accessKeyId ;
//身份密鑰
private String accessKeySecret ;
//存儲空間
private String bucketName;
}
????? AliOSSUtils工具類
@Component //當前類對象由Spring創(chuàng)建和管理
public class AliOSSUtils {
@Autowired
private AliOSSProperties aliOSSProperties;
/**
* 實現上傳圖片到OSS
*/
public String upload(MultipartFile multipartFile) throws
IOException {
// 獲取上傳的文件的輸入流
InputStream inputStream = multipartFile.getInputStream();
// 避免文件覆蓋
String originalFilename =
multipartFile.getOriginalFilename();
String fileName = UUID.randomUUID().toString() +
originalFilename.substring(originalFilename.lastIndexOf("."));
//上傳文件到 OSS
OSS ossClient = new
OSSClientBuilder().build(aliOSSProperties.getEndpoint(),
aliOSSProperties.getAccessKeyId(),
aliOSSProperties.getAccessKeySecret());
ossClient.putObject(aliOSSProperties.getBucketName(),
fileName, inputStream);
//文件訪問路徑
String url =aliOSSProperties.getEndpoint().split("http://")[0] +
"http://" + aliOSSProperties.getBucketName() + "." +
aliOSSProperties.getEndpoint().split("http://")[1] + "/" + fileName;
// 關閉ossClient
ossClient.shutdown();
return url;// 把上傳到oss的路徑返回
}
}
????當我們在項目當中要使用阿里云OSS,就可以注入AliOSSUtils工具類來進行文件上傳。但這種方式其實是比較繁瑣的。
????大家再思考,現在我們使用阿里云OSS,需要做這么幾步,將來大家在開發(fā)其他的項目的時候,你使用阿里云OSS,這幾步你要不要做?當團隊中其他小伙伴也在使用阿里云OSS的時候,步驟 不也是一樣的。
????所以這個時候我們就可以制作一個公共組件(自定義starter
)。starter
定義好之后,將來要使用阿里云OSS進行文件上傳,只需要將起步依賴引入進來之后,就可以直接注入AliOSSUtils
使用了。
需求明確了,接下來我們再來分析一下具體的實現步驟:
????第1步:創(chuàng)建自定義starter
模塊(進行依賴管理)
????????? 把阿里云OSS所有的依賴統(tǒng)一管理起來
????第2步:創(chuàng)建autoconfigure
模塊
????????? 在starter
中引入autoconfigure
(我們使用時只需要引入starter起步依賴即可)
????第3步:在autoconfigure
中完成自動配置
????????? 1. 定義一個自動配置類,在自動配置類中將所要配置的bean
都提前配置好
????????? 2. 定義配置文件,把自動配置類的全類名定義在配置文件中
我們分析完自定義阿里云OSS自動配置的操作步驟了,下面我們就按照分析的步驟來實現自定義starter
。
3.2.4.2. 自定義starter實現
????自定義starter
的步驟我們剛才已經分析了,接下來我們就按照分析的步驟來完成自定義starter
的開發(fā)。
????
首先我們先來創(chuàng)建兩個Maven模塊:
????1). aliyun-oss-spring-boot-starter模塊
注意:上面這一步,如果Spring Boot沒有2.7.5版本,就先用有的,生成maven包后再pom.xml文件中修改成想要的版本即可。
創(chuàng)建完starter模塊后,刪除多余的文件,最終保留內容如下:
- -注意- -
如果沒有.iml文件
,采用如下操作:
????按兩下ctrl,在彈出的窗口右上角點擊project,在下拉列表中選擇需要生成.iml文件
的模塊,左邊運行寫 :mvn idea:module
,然后回車運行,即可生成.iml文件
刪除pom.xml文件中多余的內容后:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
????
????2). aliyun-oss-spring-boot-autoconfigure模塊
創(chuàng)建完starter模塊后,刪除多余的文件,最終保留內容如下:
- -注意- -
如果沒有.iml文件
,采用如下操作:
????按兩下ctrl,在彈出的窗口右上角點擊project,在下拉列表中選擇需要生成.iml文件
的模塊,左邊運行寫 :mvn idea:module
,然后回車運行,即可生成.iml文件
刪除pom.xml文件中多余的內容后:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
按照我們之前的分析,是需要在starter
模塊中來引入autoconfigure
這個模塊的。打開starter
模塊中的pom文件
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!--引入autoconfigure模塊-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-bootautoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
前兩步已經完成了,接下來是最關鍵的就是第三步:
在autoconfigure模塊
當中來完成自動配置操作。
我們將之前案例中所使用的阿里云OSS部分的代碼直接拷貝到
autoconfigure模塊
下,然后進行改造就行了。
拷貝過來后,還缺失一些相關的依賴,需要把相關依賴也拷貝過來:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入web起步依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
</project>
現在大家思考下,在類上添加的@Component注解
還有用嗎?
答案:沒用了。 在SpringBoot項目中,并不會去掃描com.aliyun.oss
這個包,不掃描這個包那類上的注解也就失去了作用。
@Component注解不需要使用了,可以從類上刪除了。
????
刪除后報紅色錯誤,暫時不理會,后面再來處理。
刪除AliOSSUtils類中的@Component注解、@Autowired注解
下面我們就要定義一個自動配置類AliOSSAutoConfiguration
了,在自動配置類當中來聲明AliOSSUtils的bean對象。
????
AliOSSAutoConfiguration類:
@Configuration//當前類為Spring配置類
@EnableConfigurationProperties(AliOSSProperties.class)//導入AliOSSProperties類,并交給SpringIOC管理
public class AliOSSAutoConfiguration {
//創(chuàng)建AliOSSUtils對象,并交給SpringIOC容器
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties
aliOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}
AliOSSProperties類:
/*阿里云OSS相關配置*/
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
//區(qū)域
private String endpoint;
//身份ID
private String accessKeyId;
//身份密鑰    
private String accessKeySecret;
//存儲空間
private String bucketName;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}
AliOSSUtils類:
public class AliOSSUtils {
//注入配置參數實體類對象
private AliOSSProperties aliOSSProperties;
public AliOSSProperties getAliOSSProperties() {
return aliOSSProperties;
}
public void setAliOSSProperties(AliOSSProperties aliOSSProperties) {
this.aliOSSProperties = aliOSSProperties;
}
/**
* 實現上傳圖片到OSS
*/
public String upload(MultipartFile file) throws IOException {
//獲取阿里云OSS參數
String endpoint =aliOSSProperties.getEndpoint();
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String bucketName = aliOSSProperties.getBucketName();
// 獲取上傳的文件的輸入流
InputStream inputStream = file.getInputStream();
// 避免文件覆蓋
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
//上傳文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件訪問路徑
String url = endpoint.split("http://")[0] + "http://" + bucketName + "." + endpoint.split("http://")[1] + "/" + fileName;
// 關閉ossClient
ossClient.shutdown();
return url;// 把上傳到oss的路徑返回
}
}
?
在aliyun-oss-spring-boot-autoconfigure模塊中的resources下,新建自動配置文件:
????METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.aliyun.oss.AliOSSAutoConfiguration
3.2.4.3 自定義starter測試
阿里云OSS的starter
我們剛才已經定義好了,接下來我們就來做一個測試。
????今天的課程資料當中,提供了一個自定義starter的測試工程。我們直接打開文件夾,里面有一個測試工程。測試工程就是springboot-autoconfiguration-test,我們只需要將測試工程直接導入到Idea當中即可。
測試前準備:
????1. 在test工程中引入阿里云starter依賴
????????通過依賴傳遞,會把autoconfigure依賴也引入了
<!--引入阿里云OSS起步依賴-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
????2. 在test工程中的application.yml文件中,配置阿里云OSS配置參數信息(從以前的工程中拷貝即可)
#配置阿里云OSS參數
aliyun:
oss:
endpoint: https://oss-cn-shanghai.aliyuncs.com
accessKeyId: LTAI5t9xxxxxq5T2Av5GLDxX
accessKeySecret: C0IrHzKxxxxx8S7YQcevcotD3Zd5Tc
bucketName: web-framework01
????3. 在springboot-autoconfiguration-test工程中的com.itheima.controller.UploadController類編寫代碼
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/upload")
public String upload(MultipartFile image) throws Exception {
//上傳文件到阿里云 OSS
String url = aliOSSUtils.upload(image);
return url;
}
}
編寫完代碼后,我們啟動當前的SpringBoot測試工程:
????隨著SpringBoot項目啟動,自動配置會把AliOSSUtils的bean對象裝配到IOC容器中
注意,如果在運行過程出現如下錯誤:
????java: 程序包com.aliyun.oss 不存在
解決辦法:
????刪除項目中的.iml
文件,然后重新創(chuàng)建
用postman工具進行文件上傳:
通過斷點可以看到自動注入AliOSSUtils的bean對象:
4. Web后端開發(fā)總結
????到此基于SpringBoot進行web后端開發(fā)的相關知識我們已經學習完畢了。下面我們一起針對這段web課程做一個總結。
????我們來回顧一下關于web后端開發(fā),我們都學習了哪些內容,以及每一塊知識,具體是屬于哪個框架的。
????web后端開發(fā)現在基本上都是基于標準的三層架構進行開發(fā)的,在三層架構當中,Controller控制器
層負責接收請求響應數據,Service業(yè)務層
負責具體的業(yè)務邏輯處理,而Dao數據訪問層
也叫持久層,就是用來處理數據訪問操作的,來完成數據庫當中數據的增刪改查操作。
????在三層架構當中,前端發(fā)起請求首先會到達
Controller
(不進行邏輯處理),然后Controller
會直接調用Service
進行邏輯處理,Service
再調用Dao
完成數據訪問操作。
????如果我們在執(zhí)行具體的業(yè)務處理之前,需要去做一些通用的業(yè)務處理,比如:我們要進行統(tǒng)一的登錄校驗,我們要進行統(tǒng)一的字符編碼等這些操作時,我們就可以借助于Javaweb當中三大組件之一的過濾器Filter
或者是Spring當中提供的攔截器Interceptor
來實現。
????而為了實現三層架構層與層之間的解耦
,我們學習了Spring框架當中的第一大核心:IOC控制反轉
與DI依賴注入
。
????所謂控制反轉,指的是將對象創(chuàng)建的控制權由應用程序自身交給外部容器,這個容器就是我們常說的IOC容器或Spring容器。
????而DI依賴注入指的是容器為程序提供運行時所需要的資源。
????除了IOC
與DI
我們還講到了AOP面向切面編程,還有Spring中的事務管理、全局異常處理器,以及傳遞會話技術Cookie
、Session
以及新的會話跟蹤解決方案JWT令牌
,阿里云OSS對象存儲服務,以及通過Mybatis持久層
架構操作數據庫等技術。
????我們在學習這些web后端開發(fā)技術的時候,我們都是基于主流的SpringBoot進行整合使用的。而SpringBoot又是用來簡化開發(fā),提高開發(fā)效率的。像過濾器、攔截器、IOC、DI、AOP、事務管理等這些技術到底是哪個框架提供的核心功能?
????
Filter過濾器
、Cookie
、Session
這些都是傳統(tǒng)的JavaWeb提供的技術。
????JWT令牌
、阿里云OSS對象存儲服務
,是現在企業(yè)項目中常見的一些解決方案。
????IOC控制反轉
、DI依賴注入
、AOP面向切面編程
、事務管理
、全局異常處理
、攔截器
等,這些技術都是 Spring Framework框架當中提供的核心功能。
????Mybatis
就是一個持久層的框架,是用來操作數據庫的。
????在Spring框架的生態(tài)中,對web程序開發(fā)提供了很好的支持,如:全局異常處理器、攔截器這些都是Spring框架中web開發(fā)模塊所提供的功能,而Spring框架的web開發(fā)模塊,我們也稱為:SpringMVC
SpringMVC不是一個單獨的框架,它是Spring框架的一部分,是Spring框架中的web開發(fā)模塊,是用來簡化原始的Servlet程序開發(fā)的。
????外界俗稱的SSM
,就是由:SpringMVC、Spring Framework、Mybatis三塊組成。
????基于傳統(tǒng)的SSM
框架進行整合開發(fā)項目會比較繁瑣,而且效率也比較低,所以在現在的企業(yè)項目開發(fā)當中,基本上都是直接基于SpringBoot整合SSM進行項目開發(fā)的。
????到此我們web后端開發(fā)的內容就已經全部講解結束了。文章來源:http://www.zghlxwxcb.cn/news/detail-807653.html
資料來源:b站黑馬程序員文章來源地址http://www.zghlxwxcb.cn/news/detail-807653.html
到了這里,關于【JavaWeb后端開發(fā)-第七章】SpingBoot原理的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!