SpringBoot隨手筆記

卡牌300發表於2024-05-09

SpringBoot隨手筆記

0 關於火狐瀏覽器什麼時候會發出http請求的說明

在抓包的情況下(按下F12後的模式),不管是重新整理頁面還是在瀏覽器位址列回車,該頁面中的圖片都會發出http請求;

但如果不是抓包的模式下,如果訪問的頁面和上一次訪問的頁面相同(位址列的地址沒有更改),不管是重新整理頁面還是在瀏覽器位址列回車該頁面中的圖片都不會再次發出http請求,而是使用的是瀏覽器快取的圖片

image-20231209170132470

//很多瀏覽器 在url沒有變化的時候 ,圖片不會發出新的請求
//雖然是無痕瀏覽但是 因為圖片被快取了 是沒辦法的
//瀏覽器認為返回的就是一個圖片 因此請求就不再發出了
//為了防止不請求 不重新整理 可以攜帶一個變化的引數
//this.src="http://localhost:8080/jiaju_mall/kaptchaServlet";
jiaju_mall專案中解決兩個奇怪的問題其一
<%--img src="#" 因為寫了base標籤 所以參考base標籤後,
會去請求當前頁url http://localhost:8080/jiaju_mall/# --%>
<%--<img src="#" alt="">--%>
<!--下面這個會發出http請求,請求當前頁面-->
<img src="#"/>
<!--下面這個src為空,不會發出http請求-->
<img src=""/>

如果是在瀏覽器位址列回車請求一個圖片/.css檔案,http://localhost:8080/images/logo.png 並且這個圖片有快取了,這個http請求就不會被髮出;

但如果是重新整理頁面,就會發出該請求。

image-20231212220406379


只要我們在程式碼中建立了一個img元素(HTMLImageElement例項),並且我們給這個img元素的src賦值了,那麼瀏覽器就會發起網路請求,載入圖片內容


0.1為什麼傳統方式配置的檢視解析器進行檢視解析後,在底層請求轉發後可以被攔截器攔截到,而透過 spring-boot-starter-thymeleaf 自動配置 ,Thymeleaf自帶的檢視解析器就不會被攔截器攔截!!

主要的原因在於 Thymeleaf 的工作機制與傳統的檢視解析器有所不同。

  • 請求轉發 vs 模板渲染: 傳統的檢視解析器(如 InternalResourceViewResolver)會使用請求轉發來將請求轉發到特定的 JSP 或 HTML 頁面。這時,攔截器可以捕獲到請求的轉發並執行相應的操作。 與此不同,Thymeleaf 作為一個模板引擎,它的工作方式是將模板渲染成最終的 HTML 輸出,而不涉及到請求轉發。Thymeleaf 檢視解析器只負責解析檢視名稱對應的模板,並將渲染結果返回給客戶端。

  • 生命週期階段: 攔截器的執行生命週期在請求的處理前和處理後。對於傳統的檢視解析器,請求轉發是在處理階段進行的,因此攔截器可以捕獲到。然而,Thymeleaf 的工作是在請求處理之後的渲染階段,此時攔截器已經完成了它的生命週期。 由於這些差異,攔截器在 Thymeleaf 檢視渲染階段沒有機會進行攔截。Thymeleaf 的設計目標是提供靈活且強大的模板渲染功能,而不一定與傳統的請求轉發方式完全一致。

  • 如果你需要在 Thymeleaf 檢視渲染之前或之後執行一些操作,可以考慮使用 Thymeleaf 提供的一些擴充套件機制,如 ITemplateResolver、IProcessor 等。這些擴充套件點可以在模板渲染的不同階段新增自定義邏輯。

1SpringBoot啟動失敗,報錯

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hspedu</groupId>
<artifactId>quickstart</artifactId>
<version>1.0-SNAPSHOT</version>


<!--匯入springboot父工程-規定寫法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>

<!--這裡我們可以指定mysql.version-->
<!--<properties>-->
<!-- <mysql.version>5.1.49</mysql.version>-->
<!--</properties>-->


<!--匯入web專案場景啟動器: 會自動匯入和web開發相關的所有依賴[庫/jar]
後面老師還會說spring-boot-starter-web 到底引入哪些相關依賴
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

報錯下面的語句並輸出三行日誌就執行結束

error statuslogger log4j2 could not find a logging implementation. please add log4j-core to the classpath. using simplelogger to log to the console...

image-20231121164053601

解決方法 重新整理Maven依賴!

image-20231121163951252


2 maven專案啟動後全域性都爆紅

image-20231121232438853

image-20231121232501510

image-20231121232637650

因為把上一級的普通檔案目錄當作idea專案檔案開啟了,生成了 .idea檔案,破壞了專案原有的結構,這時只需要將在maven專案上一級的普通資料夾(my_mybatis資料夾)中 生成的.idea檔案刪除Build Project即可解決!!!

image-20231121234342298

參考csdn https://blog.csdn.net/qq_35077107/article/details/111657346

執行報錯常用解決辦法:

  1. 刪除專案資料夾中的 .idea, .iml 檔案, 然後重新用 IDEA 開啟專案

  2. 重新 build 專案, 見下圖:

  1. 雙擊 maven 中 lifecycle 中的 clean ,清理專案, 見下圖:

  1. 如果還不行, 可以重啟下電腦試一下, 有時候會有這種奇怪的問題, 本身設定都沒錯, 但就是報 無法載入主類的錯誤.

  2. 最後再不行, 建議重新建立一個 maven工程, 然後將要複製的檔案內容手動複製到新專案中, 這樣一般最終都可以, 就是略麻煩. 參考:

[idea] 解決 idea 複製進專案的檔案執行時無法找到的問題


3 json字串處理 [ArrayList] 轉成json格式資料

// 把result [ArrayList] 轉成json格式資料 => 返回
// 這裡我們需要使用到java中如何將ArrayList 轉成 json
// 這裡我們需要使用jackson包下的工具類可以輕鬆搞定
// 老師演示如何操作
ObjectMapper objectMapper = new ObjectMapper();
String resultJson = objectMapper.writeValueAsString(result);

位置 D:\Java_developer_tools\ssm\my_springmvc\hsp_springmvc\src\main\java\com\hspedu\JacksonTest.java

需要匯入 相關的 jar jackson

 public static void main(String[] args) {

ArrayList<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100,"牛魔王","牛魔王拳",400));
monsters.add(new Monster(200,"蜘蛛精","吐口水",200));

//注意引入的是 com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
try {
//把monsters轉成json
String monstersJson = objectMapper.writeValueAsString(monsters);

System.out.println("monstersJson= " + monstersJson);
//monstersJson=
// [{"id":100,"name":"牛魔王","skill":"牛魔王拳","age":400},
// {"id":200,"name":"蜘蛛精","skill":"吐口水","age":200}]
} catch (Exception e) {
e.printStackTrace();
}

}

4 springboot中 classpath: 和 file: 分別指的是類路徑和專案根路徑

#springboot在哪裡配置讀取application.properties檔案:
#public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
# 解讀 "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
# 類路徑、類路徑下的/config/ ,file路徑 就是專案根路徑
# ,預設在這些路徑下可以讀取到 application.properties 檔案
# private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";

# 這裡指定了 springboot預設配置檔名要求叫做 application .properties 而不能亂寫的原因
# private static final String DEFAULT_NAMES = "application";

image-20231127134356010

image-20231127134544548


5 想建立一個springboot的專案,本地安裝的是1.8,但是在使用Spring Initializr建立專案時,發現版本只有17和21。

起因

想建立一個springboot的專案,本地安裝的是1.8,但是在使用Spring Initializr建立專案時,發現版本只有17和21。

image-20231202141315140

在JDK為1.8的情況下,無論選擇Java17版本或者21版本時,都會報錯。 要求你要麼選擇更低的Java版本或者更換更高的SDK版本即跟換JDK版本

img

Java17和Java 8(JDK1.8)的區別

版本號:Java 17 是 Java SE 17 的版本,而 JDK 1.8 是 Java SE 8 的版本。

釋出時間:Java 17 釋出於 2021 年,而 JDK 1.8 釋出於 2014 年.

新特性:Java 17 相對於 JDK 1.8,新增了很多特性和改進,例如:Switch 表示式、Sealed 類、Pattern Matching for instanceof 等。

相容性:Java 17 和 JDK 1.8 不相容,由於 Java 的向後相容性,Java 17 可以執行 JDK 1.8 的程式碼,但 JDK 1.8 不支援 Java 17 的新特性。

安全性:Java 17 相對於 JDK 1.8,修復了更多的安全漏洞,提高了程式的安全性。

exe和msi的區別

"exe"和"msi"是兩種常見的副檔名,用於Windows作業系統中的安裝程式。它們之間有以下區別:

格式:exe檔案是可執行檔案,而msi檔案是Windows Installer安裝包。 安裝方式:exe檔案通常是自解壓縮檔案,一般包含一個可執行檔案和其他必要的資源。當你執行exe檔案時,它會解壓縮並執行其中的程式來完成安裝。msi檔案是一種基於Windows Installer技術的安裝包,它採用了Windows Installer服務來處理安裝和解除安裝操作。 安裝過程:exe檔案通常是自包含的安裝程式,可以執行多個操作,例如複製檔案、建立登錄檔項、安裝驅動程式等。msi檔案則使用Windows Installer提供的功能,透過一系列的安裝動作和操作來完成安裝過程。這包括執行預定義的安裝指令碼、處理檔案和登錄檔項、建立快捷方式等。 高階功能:由於採用了Windows Installer技術,msi檔案支援一些高階功能,例如安裝時的自定義設定、升級和修補程式、回滾機制等。這些功能使得msi檔案更適合在企業環境中進行軟體分發和管理。 總的來說,exe檔案更加靈活,適用於簡單的安裝過程,而msi檔案提供了更強大和可定製的安裝功能,適用於需要管理和分發的複雜軟體。在選擇使用哪種檔案格式時,應根據具體的需求和情況進行評估。

分析

  1. Spring官方釋出Spring Boot 3.0.0 的時候告知了一些情況,Java 17將成為未來的主流版本,所有的Java EE Api都需要遷移到Jakarta EE上來。大部分使用者需要修改import相關API的時候,要用jakarta替換javax。比如:原來引入javax.servlet.Filter的地方,需要替換為jakarta.servlet.Filter

  2. 進入Springboot官網檢視情況,發現在2023年11月24日,3.0以下的版本不再支援了。

    img

解決 方案一:替換建立專案的源 我們只知道IDEA頁面建立Spring專案,其實是訪問spring initializr去建立專案。故我們可以透過阿里雲國服去間接建立Spring專案。將https://start.spring.io/或者http://start.springboot.io/替換為 https://start.aliyun.com/

image-20231202141935979

image-20231202142043591

方案二:升級JDK版本 採用JDK17或者21版本,建立專案後,springboot的版本要改為3.0以下,在pom.xml中把java改為1.8,如下圖。

在這裡插入圖片描述

1、下載JDK17,JDK官網 選擇Windows X64 Installer下載即可。

在這裡插入圖片描述

2、安裝JDK17 雙擊已下載的jdk17,進入安裝介面,點選下一步 ==> 選擇安裝路徑,可更改路徑,點選更改,我選擇安裝在E盤,點選確定 == > 點選下一步進行安裝。安裝完成後直接點選關閉即可。

img

3、配置Java環境變數

方式一:點選開始設定 ==> 搜尋環境變數 方式二:右擊此電 ==> 屬性 ==> 高階系統設定 ==>環境變數

在這裡插入圖片描述

img

在這裡插入圖片描述

在系統變數中找到ptah雙擊,進入path頁面,新增jdk安裝位置,新建,選擇自己的安裝JDK17位置如下圖

img

在這裡插入圖片描述

4、驗證是否配置成功 win+R輸入cmd回車,輸入java -version和javac -version,均返回jdk版本,如下圖

在這裡插入圖片描述

參考文獻 jdk17下載及環境變數配置 exe和msi的區別


6 IDEA2020.2版本建立/開啟高版本Spring Boot專案 3.2.0 卡死

構建時,IDEA卡死無響應 在這裡插入圖片描述 把專案中的.mvn資料夾刪除即可 在這裡插入圖片描述 應該是這個版本的問題

第二種 在idea中使用Spring Initializr 建立的高版本springboot專案 卡死

image-20231202152254798

IDEA2020.2建立springboot專案卡死在reading maven project的問題

解決方法一

問題描述: 昨天更新IDEA2020.2版本後,建立springboot專案的時候發現一直在reading maven project 中,如下圖,而且一點setting(想修改本地maven路徑)時,IDEA就卡死,而且開啟工作管理員發現IDEA高佔CPU。

img

原因: 猜測是2020.2版本問題,用其他版本沒有出現這類情況。 由於第一次用spring Initializr建立maven的專案,它不會找IDEA自帶的maven,也不會找你配置的本地maven,而是重新下載一個全新的maven(而且用的是外網下載,特別慢,也導致電腦卡)

我是w10的,系統盤在C 下載的目錄:C:\Users\Administrator.m2\wrapper\dists\apache-maven-3.6.3-bin\1iopthnavndlasol9gbrbg6bf2

img

快速解決:直接刪除工程目錄maven-wrapper.properties 檔案;

img

解決方法:

C:\Users\Administrator\.m2\ 1、找的這個目錄,bin下面的這層目錄大家可能不一樣,在這個目錄把maven手動放進去,下次建立就不會再下載了而卡死了。

img

2、不要關閉IDEA,讓它自己慢慢下載,慢慢的等待吧…一個小時估計下載好了。。。。 3、換個版本,我用過的2019.3或者2020.1都沒有這些問題、

下載壓縮版apache-maven-3.6.3:

注意:因某些限制,某連結透過如下方式獲取:

遺留問題:

1、我改了很多次setting,或者setting for new project 結果重啟或者重新用spring Initializr建立還是恢復成這個路徑,其實保證本地的maven也是3.6.3不會造成影響,修改下面兩項是本地的就行。倉庫還是用本地的,某還是用本地setting配置的阿里雲。

2、如果有更好的解決方案會及時更新。

解決方法二

附錄:如果上述方法還是幫不到你,可以嘗試下面的解決方法@2

idea2020.2卡死在reading maven projects | idea 2020.2正在讀取maven專案 卡住

這是Idea2020.2版本的官方bug,經過多方案測試,我已經解決,下面分享下經驗。

我的方法:修改Host檔案

1.結束idea的程序

2.將所有指向127.0.0.1的網址注掉,並新增一條新紀錄,將127.0.0.1指向localhost

3.儲存並重啟Idea

提示:修改host檔案需要管理員許可權(否則系統會提示你需要另存為),這類基礎操作自行百度即可。

網友的方法:

移除專案下 .mvn/maven-wrapper.properties 檔案,重啟IDEA

解決方法三

idea2020.2建立springboot專案開啟卡死在reading maven projects;

解決方法:

移除工程目錄/.mvn/maven-wrapper.properties 檔案,重新開啟idea,再根據路徑open一下專案


7 IDEA異常關閉導致的8080埠占用可以開啟工作管理員 輸入 J 找到正在執行的java程式 將其關閉即可

image-20231202221102292


8 StringUtils.hasText() 和 ObjectUtils.isEmpty()

StringUtils和ObjectUtils都是spring框架提供的工具類

package org.springframework.util;



// 做一個判斷
// 如果 imgPath 有資料 就設定給this.imgPath
// 如果 null 或 空串"" 或 都是空格" " 就使用預設的圖片路徑

//使用spring框架提供的工具類 StringUtils hasText()方法
//解決
// import org.springframework.util.StringUtils;
/**
* public static boolean hasText(@Nullable String str) {
* return str != null && !str.isEmpty() && containsText(str);
* }
*
* StringUtils.hasText() 會經常使用!!
* 該方法 如果傳入的是 null 或 空串"" 或 都是空格" " 返回false 即
* 如果是Text 有資料 ,才會返回true
*/
if (StringUtils.hasText(imgPath)) {
this.imgPath = imgPath;
}

9 自定義物件引數 封裝成物件

<h1>新增妖怪-坐騎[測試封裝 POJO;]</h1>
<form action="/saveMonster" method="post">
編號: <input name="id" value="100"><br/>
姓名: <input name="name" value="牛魔王!"/> <br/>
年齡: <input name="age" value="120"/> <br/>
婚否: <input name="isMarried" value="true"/> <br/>
生日: <input name="birth" value="2000/11/11"/> <br/>


坐騎:<input name="car.name" value="法拉利2"/><br/>
價格:<input name="car.price" value="99999.2"/>

<input type="submit" value="儲存"/>
</form>

 /**
* 演示自定義物件引數使用,完成自動封裝,型別轉換 從save.html傳送請求進行測試
* 形參位置直接寫 Monster monster, 就可以,底層會自動完成封裝
* 注意前端提交引數的引數名 必須和物件的屬性名保持一致 否則封裝失敗
* 如果有級聯 就按照 car.name car.price 的方式進行資料的提交 否則封裝失敗
*
* 注意:這種封裝物件的方式,不需要把相關的物件注入到容器中,會自動地完成封裝
*/
@PostMapping("/saveMonster")
public String saveMonster(Monster monster){

System.out.println("monster= " + monster);
return "success";
}


10 @ResponseBody註解返回json/xml格式資料

//返回Monster物件-以json格式返回

/**
* 底層會走到 AbstractJackson2HttpMessageConverter轉換器類的 writeInternal方法
*
*
*/
@GetMapping("/get/monster")
@ResponseBody
public Monster getMonster(){

//開發中,monster物件是從DB中獲取的,這裡模擬monster資料
Monster monster = new Monster();
monster.setId(100);
monster.setName("孫悟空");
monster.setAge(10000);
monster.setBirth(new Date());
monster.setIsMarried(false);
Car car = new Car();
car.setName("賓士");
car.setPrice(200000.0);
monster.setCar(car);

return monster;
}

@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {

MediaType contentType = outputMessage.getHeaders().getContentType();
// contentType 其實就是http協議中請求頭設定的 Accept的值
// 根據contentType決定要給瀏覽器/客戶端返回的資料格式 [json/xml...]

JsonEncoding encoding = getJsonEncoding(contentType);

Class<?> clazz = (object instanceof MappingJacksonValue ?
((MappingJacksonValue) object).getValue().getClass() : object.getClass());
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());

OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
try (JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding)) {
// JsonGenerator 容易產生誤解
// 並不是只可以處理Json格式的資料,xml也可以處理,只不過
// 這裡統一使用JsonGenerator進行處理
// 根據contentType不一樣,產生的generatorbu'yi'y
// 如果返回的是json格式這裡產生的 generator物件 是 UTF8JsonGenerator
// 如果返回的是xml格式這裡產生的 generator物件 是 ToXmlGenerator(前提是
// 在pom.xml檔案中引入了支援返回xml格式的依賴jackson-dataformat-xml)


writePrefix(generator, object);

Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;

if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}

ObjectWriter objectWriter = (serializationView != null ?
objectMapper.writerWithView(serializationView) : objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
objectWriter.writeValue(generator, value); //重要的,在這裡返回資料

writeSuffix(generator, object);
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}

沒有加@ResponseBody註解時 上面writeInternal()方法形參中的 object如下:

image-20231206210334840

加了@ResponseBody註解時 上面writeInternal()方法形參中的 object如下:

image-20231206210338238


11 瀏覽器發出第一次請求,在伺服器端進行了重定向,瀏覽器根據響應碼302和Location發出的第二次請求是get請求


12 form表單action如果寫了一個# 預設會提交給當前這個斜槓 /

<!--action="#" 預設會提交給當前這個斜槓 / 
, 因為後端的@GetMapping(value = {"/","/login"})可以匹配到
, 所以預設不改 也是可以提交到的

-->
<!--<form action="#" method="post">-->
<!--<form action="#" th:action="/login" method="post">-->
<form action="/login" method="post">
<label style="color: #ff0000" th:text="${msg}"></label><br/>
<!--如果這裡寫了 th:text="" 就會報錯
org.thymeleaf.templateengine : [thymeleaf][http-nio-8080-exec-1] exception processing template "adminlogin": could not parse as expression: "" (template: "adminlogin" - line 17, col 32)
org.thimeleaf.templateengine:[thymelaf][http-nio-8080-exec-1]異常處理模板“adminlogin”:無法解析為表示式:“”(模板:“adminlogin”-第17行,第32列
-->
<!--使用者名稱:<input type="text" th:text="" style="width:150px" name="name"/><br/><br/>-->
使用者名稱:<input type="text" style="width:150px" name="name"/><br/><br/>
密 碼:<input type="password" style="width:150px" name="password"/><br/><br/>
<input type="submit" value="登入"/>
<input type="reset" value="重新填寫"/>
</form>

@GetMapping(value = {"/","/login"})
public String login(){

/**
* 為什麼這裡沒有配置檢視解析器,可以找到 thymeleaf/adminLogin.html檔案呢?
* 1. 因為我們引入了starter-thymeleaf
* 2. 這裡就會直接使用檢視解析到 thymeleaf下的模板檔案adminLogin.html
* 3. starter-thymeleaf它本身就進行了檢視解析
*
*/
//這裡不用寫所在的包,可以直接寫裡面的thymeleaf檔案 adminLogin.html 的檔名而不用寫包名 回顧檢視解析器 有字首和字尾
return "adminLogin";
}


13 思考的問題,如果上傳了兩個相同檔名的檔案,再往回下載回來,如何進行區分?


14 springboot專案中獲取類路徑的方法ResourceUtils.getURL()

import org.springframework.util.ResourceUtils;

String path = ResourceUtils.getURL("classpath:").getPath();
log.info("path={}",path);

15 建立檔案時 路徑中的正斜槓"/"和兩個反斜槓"\"效果是一樣的,都可以建立成功!!

建立檔案時 路徑中的正斜槓"/"和兩個反斜槓"\\"效果是一樣的,都可以建立成功!!
File uploadPhoto = new File(file.getAbsolutePath() + "\\" + originalFilename);
File uploadPhoto2 = new File(file.getAbsolutePath() + "/" + originalFilename);
log.info("uploadPhoto 絕對路徑={}", uploadPhoto.getAbsolutePath());
log.info("uploadPhoto2 絕對路徑={}", uploadPhoto2.getAbsolutePath());

輸出如下:
uploadPhoto 絕對路徑=D:\Java_developer_tools\springboot\springboot-usersys\target\classes\static\images\upload\浴衣.jpg

uploadPhoto2 絕對路徑=D:\Java_developer_tools\springboot\springboot-usersys\target\classes\static\images\upload\浴衣.jpg


16 注入Mapper介面的代理物件的方式對比@Mapper&MapperScannerConfigurer

@Mapper介面的作用和ssm整合專案中spring配置檔案中的

 <!-- 配置掃描器,將 mybatis 在dao層 介面的實現加入到 ioc 容器中    1. 我們的mapper介面放在com.hspedu.furn.dao    2. mybatis就是處於DAO層,操作DB  -->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 1. 掃描所有的 dao 介面的實現,加入到 ioc 容器中 2. 這裡 dao 介面,就是 mapper 介面 -->
<property name="basePackage" value="com.hspedu.furn.dao"/>
</bean>

是否相同?

艾麗絲格雷拉特:

@Mapper 註解和配置 MapperScannerConfigurer 兩種方法都用於在 MyBatis 和 Spring 整合中掃描和註冊 Mapper 介面,但它們的使用方式和上下文有所不同。

@Mapper 註解

  • 用途@Mapper 註解直接標記在 Mapper 介面上。

  • 作用:這個註解告訴 MyBatis,該介面是一個 Mapper 介面,MyBatis 在啟動時會自動掃描這些標記了 @Mapper 的介面,併為它們建立代理物件。

  • 優勢:使用 @Mapper 註解的主要優勢是簡單明瞭,直接在介面上宣告,無需其他配置。

  • 缺點:需要在每個 Mapper 介面上單獨新增註解,如果 Mapper 介面很多,可能會稍顯麻煩。

MapperScannerConfigurer 配置

  • 用途MapperScannerConfigurer 是在 Spring 配置檔案中配置的。

  • 作用:它用於指定一個包路徑,Spring 啟動時會掃描這個包及其子包下所有的介面,如果它們是 MyBatis 的 Mapper 介面,Spring 會為它們建立代理物件並加入到 IOC 容器中。

  • 優勢:使用 MapperScannerConfigurer 的主要優勢是集中管理,可以一次性掃描整個包路徑下的所有 Mapper 介面,無需在每個介面上單獨新增註解。

  • 缺點:配置較為複雜,需要在 Spring 的配置檔案中進行配置。

相同點和不同點

  • 相同點:兩者的最終目的都是讓 Spring 容器管理 MyBatis 的 Mapper 介面,併為這些介面建立代理物件。

  • 不同點:使用方式不同,@Mapper 是註解方式,直接標記在介面上;而 MapperScannerConfigurer 是透過 XML 或 Java 配置的方式,在配置檔案中指定掃描的包路徑。

在 Spring Boot 專案中,通常推薦使用 @Mapper 或者使用 @MapperScan 註解在配置類上指定掃描的包路徑,這兩種方式更符合 Spring Boot 的自動配置和約定優於配置的理念。而在傳統的 Spring 專案中,使用 MapperScannerConfigurer 可能更常見。


17 模糊查詢'%%' 中間什麼都不寫預設是查詢所有記錄

-- 模糊查詢'%%' 中間什麼都不寫預設是查詢所有記錄

SELECT * FROM monster WHERE email LIKE '%%'


18 在 MyBatis-Plus 或 MyBatis 中,配置項 map-underscore-to-camel-case 主要影響的是屬性(欄位)的命名對映,而不是類名與表名的對映。


19 mabatis報錯:Result type not match for select id=“findMonsterByIdAndName“

image-20231218175228558

Monster類引錯了,引到別的module下的Monster了,重新引入一下Monster即可,重新輸入一下

Monster 兩個module下都定義了com.hspedu.entity.Monster 導致引入衝突 報錯

參考如下:

mabatis報錯:Result type not match for select id=“selectAll“

img

檢查發現返回型別沒問題啊

img

對映檔案裡的返回型別也沒問題

找了好久發現:原來我定一個兩個Brand類,在不同的modul下。

img img

修改為TBrand後,不再報錯:

img

問題解決


20 @Bean註解標註的方法是可以有引數的

在Spring框架中,@Bean註解的方法可以有引數,這些參數列示該Bean的依賴。Spring容器會自動地為這些引數尋找合適的例項來注入。這是Spring的依賴注入(Dependency Injection, DI)功能的一個關鍵方面。具體來說:

  1. 尋找合適的Bean進行注入

    • 當Spring容器建立一個由@Bean註解的方法定義的Bean時,它會檢查這個方法的引數。

    • 對於每個引數,Spring容器會在其當前管理的Bean中尋找與引數型別相匹配的Bean。

    • 如果找到了合適的Bean,Spring容器就會將這個Bean注入作為引數。

  2. 處理多個相同型別的Bean

    • 如果有多個相同型別的Bean可用,Spring容器需要額外的資訊來確定使用哪一個。這可以透過指定Bean的名稱或使用其他限定符(如@Qualifier註解)來實現。

  3. 無法找到合適的Bean時的行為

    • 如果Spring容器無法找到匹配的Bean來注入,它通常會丟擲異常,因為這通常表示配置錯誤或者所需的Bean尚未定義。

  4. 使用@ConfigurationProperties進行細粒度控制

    • 有時,您可能會看到與@Bean一起使用@ConfigurationProperties註解。這用於將外部配置(如屬性檔案中的屬性)繫結到Bean的屬性上,進一步控制Bean的配置。

總的來說,@Bean註解的依賴注入機制使得在Spring中管理和配置Bean更加靈活和強大。


21 springboot vue整合 前後端分離專案中的問題

1 一般來說靜態方法不可以使用泛型,為什麼 public static <T> Tiger2<T> useT(T data){}可以使用泛型?

User

/**
* 該類用於測試 在靜態方法中使用泛型
* @param <T>
*/
class Tiger2<T>{
T data;

public Tiger2() {
}
public <T> Tiger2(T t) {

}
//一般來說靜態方法不可以使用泛型,這裡為什麼可以使用泛型?
public static <T> Tiger2<T> useT(T data){
Tiger2<T> tiger2 = new Tiger2<>(data);
return tiger2;
}
}

GPT:

在 Java 中,靜態方法可以使用泛型,但這些泛型引數是獨立於類泛型引數的。讓我們詳細看一下您的例子以理解這一點。

Tiger2 類中,您定義了一個泛型類引數 <T>。但是,當您在靜態方法 useT 中使用泛型 <T> 時,這裡的 <T> 並不是類級別的泛型引數。它是方法級別的泛型引數,專門用於該方法。換句話說,類泛型引數 <T> 和方法泛型引數 <T> 是完全獨立的,儘管它們恰好使用了相同的字母。

同樣,在構造器 public <T> Tiger2(T t) {} 中,泛型 <T> 也是方法級別的(在這裡是構造器級別),它與類級別的泛型 <T> 無關。這意味著,即使這是一個泛型類,這個構造器可以接受任何型別的引數,而與類級別的泛型 <T> 無關。

這個設計在某些情況下很有用,因為它允許靜態方法操作不同型別的資料,而不依賴於類例項。在靜態方法的上下文中,由於沒有類例項,因此不可能使用類級別的泛型引數。因此,靜態方法定義自己的泛型引數,這些引數在每次方法呼叫時都被獨立地推斷或指定。

Tiger2 的例子中,useT 方法允許建立任何型別的 Tiger2 例項,而與類本身的泛型引數無關。這是泛型在靜態上下文中靈活應用的一個很好的例子。

Tiger<T,R,L>類中的方法 public static void show2(T t){} Tiger2<T>類中的方法 public static <T> Tiger2<T> useT(T data){} ,public static void show2(T t){)會報錯為什麼,和加了泛型的標誌<T>的useT方法的主要區別是什麼?為什麼加了<T>後就不報錯了?

GPT:
在 Java 泛型中,類級別的泛型引數和靜態方法之間存在一些關鍵的區別,這解釋了為什麼在 Tiger 類中某些靜態方法的宣告會導致錯誤,而其他則不會。

關於 public static void show2(T t){} 的問題
在 Tiger 類中,T, R, L 是類級別的泛型引數。這意味著這些泛型引數的具體型別與每個 Tiger 例項相關聯。然而,靜態方法和屬性不屬於任何例項,它們屬於類本身。因此,靜態方法或屬性無法直接訪問類級別的泛型引數,因為它們在類例項化之前就已經存在了,而這時泛型引數的型別尚未確定。

關於 public static <T> Tiger2<T> useT(T data){} 的區別
public static <T> Tiger2<T> useT(T data){} 方法宣告中的 <T> 是一個方法級別的泛型引數。這意味著每次呼叫該方法時,都會為該方法定義一個新的、獨立的泛型型別 T。這與類級別的泛型引數完全無關。因此,這種方式是允許的,因為靜態方法定義了它自己的泛型引數,不依賴於類例項。

總結
public static void show2(T t){} 會報錯,因為它試圖使用類級別的泛型引數 T,而靜態方法無法訪問與類例項相關聯的泛型型別。
public static <T> Tiger2<T> useT(T data){} 不會報錯,因為這裡的 T 是方法級別的泛型引數,獨立於類級別的泛型引數。
這種設計是 Java 泛型的一個基本特性,旨在保持型別安全,同時確保靜態成員的獨立性和泛型方法的靈活性。

2 遇到Maven匯入不進來報錯Failed to read artifact descriptor for com.fasterxml:classmate:jar:1.5.1

image-20231226154451743

測試發現 即使匯入其他依賴,比如 mybatis-plus-boot-starter 版本號 3.5.2 也匯入失敗

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<!--<version>3.4.3</version>-->
<version>3.5.2</version>
</dependency>

image-20231226155514428

解決參考文件https://stackoverflow.com/questions/41589002/failed-to-read-artifact-descriptor-for-org-apache-maven-pluginsmaven-source-plu

image-20231226155609951

image-20231226160255345

安裝 maven helper外掛

重新再匯入幾遍就好使了,注意網路連線有沒有代理之類的

22 全域性異常處理機制

1 如何在Spring Boot應用中使用@ControllerAdvice@ExceptionHandler實現全域性異常處理,以統一管理控制器層丟擲的異常,並減少程式碼冗餘同時提供一致的錯誤響應結構?

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.http.ResponseEntity;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}

@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<String> handleGeneralException(Exception ex, WebRequest request) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An internal error occurred: " + ex.getMessage());
}
}

在 Spring Boot 應用中使用 @ControllerAdvice@ExceptionHandler 實現全域性異常處理是一種高效的方式來統一處理控制器層丟擲的異常。這不僅可以減少程式碼冗餘,還能提供一致的錯誤響應結構。現在,我將詳細解釋您的程式碼及其工作原理。

全域性異常處理的工作原理

1. 使用 @ControllerAdvice

@ControllerAdvice 註解是用來配置控制器的通知,意味著它提供了一個集中的位置,所有控制器丟擲的異常都能在這裡被處理。@ControllerAdvice 可以指定監聽的包,如果沒有指定,則預設監聽所有包。這個類標記了 @ResponseBody,表明響應內容都將自動轉化為 JSON 格式。

2. 使用 @ExceptionHandler

@ExceptionHandler 註解用來定義具體處理異常的方法。這個方法可以定義多個,針對不同型別的異常進行處理。

3. 返回 R.error()

在您的全域性異常處理類 (HsplivingExceptionControllerAdvice) 中,handleValidException 方法處理 MethodArgumentNotValidException 型別的異常,通常這種異常是由於客戶端提交的資料驗證失敗(使用了 Spring 的驗證框架如 @Validated)時丟擲的。

方法內部:

  • 日誌記錄:首先可能會記錄一些日誌(這部分在程式碼中被註釋掉了)。

  • 錯誤資訊收集:收集所有欄位驗證失敗的資訊,並將其儲存在一個 Map 中。

  • 返回錯誤響應:使用 R.error() 構建一個錯誤響應物件,這個物件包含錯誤程式碼、錯誤訊息和詳細的驗證錯誤資訊。

這裡的 R 類似是一個響應構造器,它可能是一個自定義的響應體結構,通常包含如下幾部分:

  • 狀態碼(如 200、400、500)

  • 訊息(通常是錯誤訊息或成功訊息)

  • 資料(返回給前端的任何相關資料)

返回資料到前端的過程

當一個異常被 @ExceptionHandler 捕獲時:

  1. 異常處理方法被呼叫:對應的處理方法被呼叫,執行方法內部的程式碼。

  2. 構建響應體:方法執行完成後,返回的 R 物件會被 Spring MVC 轉換為 JSON 格式的資料。這一轉換是由 Spring MVC 內建的訊息轉換器(通常是 Jackson2HttpMessageConverter)完成的。

  3. 傳送響應到客戶端:JSON 資料作為 HTTP 響應的體(body)傳送回客戶端。

示例:錯誤處理流程

假設客戶端提交了一個不符合驗證要求的請求,MethodArgumentNotValidException 被丟擲:

  • 異常在控制器層被丟擲。

  • @ControllerAdvice 註冊的類捕獲此異常,handleValidException 方法被呼叫。

  • 方法內收集錯誤詳情,並使用 R.error() 建立含有這些資訊的響應物件。

  • 響應物件被轉換為 JSON 格式,並作為 HTTP 響應返回給客戶端。

這種方式確保了所有異常的處理都是統一和集中的,前端接收到的錯誤資訊格式一致,便於處理和展示錯誤資訊。

2 如何在Spring Boot應用中使用@ControllerAdvice實現全域性異常處理,特別是在處理異常時使用Spring MVC的檢視渲染機制返回錯誤頁面,而非直接返回JSON格式的資料?

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler({ArithmeticException.class, NullPointerException.class, AccessException.class})
public ModelAndView handleArithmeticException(Exception ex, Model model) {
model.addAttribute("error", ex.getMessage());
return new ModelAndView("error/global");
}
}

確實,您提供的這個全域性異常處理類 (GlobalExceptionHandler) 與上面討論的 JSON 響應方式不同,主要在於它使用了 Spring MVC 的檢視渲染機制來返回錯誤頁面,而不是返回 JSON 格式的響應資料。這裡的處理方式更適合傳統的 Spring MVC 應用,其中前端直接由伺服器渲染,而非前後端分離的架構。讓我們逐一解析這個類和它的功能:

功能解析

  1. @ControllerAdvice

    • 這個註解將類標記為全域性異常處理類,意味著它會攔截所有控制器丟擲的異常(除非指定了特定的包或控制器)。

  2. @ExceptionHandler

    • 這個註解用來指定該處理器可以處理哪些異常型別。在您的程式碼中,它被設定為處理 ArithmeticException, NullPointerException, 和 AccessException。這意味著只有這三種型別的異常會被這個方法處理。

  3. 方法引數

    • Exception e: 被捕獲的異常物件。

    • Model model: 一個模型物件,用來向檢視傳遞資料。

    • HandlerMethod handlerMethod: 當前處理請求的方法的相關資訊,可以用來獲取一些額外的方法級資訊,比如方法名。

  4. 異常資訊處理

    • 異常資訊被記錄到日誌中。

    • 異常資訊也被新增到模型中,這樣它就可以在檢視中被顯示。

  5. 檢視渲染

    • 返回的字串指向一個檢視名,這裡是 "error/global""/error/global"。根據您的檢視解析器配置,這會導向 templates/error/global.html 路徑下的 Thymeleaf 模板或其他檢視模板。

異常處理流程

ArithmeticException, NullPointerException, 或 AccessException 其中一種異常在控制器中被丟擲時:

  • Spring MVC 會尋找用 @ExceptionHandler 註解的方法來處理這些異常。

  • handlerAritException 方法會被呼叫,傳入異常物件、模型和處理方法的資訊。

  • 異常詳情被記錄並傳遞給檢視。

  • 方法返回檢視名,Spring MVC 根據檢視解析器配置解析該檢視並渲染,最後將渲染後的 HTML 返回給客戶端。

檢視解析和路徑問題

關於返回的檢視路徑:

  • 在 Spring Boot 中,通常檢視解析器配置了一個基礎路徑(預設是 src/main/resources/templates)和檔案字尾(預設是 .html)。

  • 因此,返回 "error/global" 通常足夠了,檢視解析器會自動尋找 templates/error/global.html

  • 如果新增了 / 字首,效果應該是相同的,除非配置的檢視解析器對此有特殊處理。

異常處理與請求轉發

在您的描述中,提到異常處理過程類似於請求轉發,但與傳統的 Servlet 請求轉發不同。確實,這種內部處理機制允許資料在伺服器內部從異常處理器傳遞到檢視渲染器,而不經過客戶端重定向或額外的 HTTP 請求,提高了處理效率並減少了客戶端所需的處理邏輯。

總結

這個全域性異常處理器是為那些需要透過服務端渲染錯誤頁面的應用設計的。它提供了一種結構化的方法來處理異常,確保使用者在面對錯誤時能獲得更友好的使用者體驗。對於前後端分離的應用,通常建議返回 JSON 物件,以便前端應用可以更靈活地處理異常展示。


3 如何在Spring Boot中使用@RestControllerAdvice實現針對RESTful API的全域性異常處理,並如何透過自定義響應體和校驗註解最佳化異常處理邏輯?

import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.validation.BindException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<RespBean> handleGeneralException(Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(RespBean.error("General Error", "An error occurred: " + e.getMessage()));
}

@ExceptionHandler(BindException.class)
public ResponseEntity<RespBean> handleBindException(BindException e) {
return ResponseEntity
.badRequest()
.body(RespBean.error("Validation Error", e.getBindingResult().toString()));
}

@ExceptionHandler(GlobalException.class)
public ResponseEntity<RespBean> handleGlobalException(GlobalException e) {
return ResponseEntity
.status(e.getStatus())
.body(RespBean.error(e.getMessage(), e.getDetails()));
}
}

public class RespBean {
private String message;
private String details;
private HttpStatus status;

public static RespBean error(String message, String details) {
RespBean resp = new RespBean();
resp.message = message;
resp.details = details;
resp.status = HttpStatus.BAD_REQUEST;
return resp;
}

// Getters and Setters
}

public class GlobalException extends RuntimeException {
private String details;
private HttpStatus status;

public GlobalException(String message, String details, HttpStatus status) {
super(message);
this.details = details;
this.status = status;
}

// Getters and Setters
}

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IsMobileValidator.class)
public @interface IsMobile {
String message() default "手機號碼格式錯誤";
boolean required() default true;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};

class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required;

@Override
public void initialize(IsMobile constraintAnnotation) {
required = constraintAnnotation.required();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (required) {
return ValidatorUtil.isMobile(value);
} else {
return !StringUtils.hasText(value) || ValidatorUtil.isMobile(value);
}
}
}
}

在您提供的這個例子中,GlobalExceptionHandler 類使用了 @RestControllerAdvice 註解,這與 @ControllerAdvice 相似,但是專門用於構建 RESTful API,通常與 @ResponseBody 結合使用,直接返回資料而不是檢視。這個處理器設計得更具體,針對不同型別的異常提供了不同的處理策略,並且返回的是自定義的響應體 RespBean。讓我們逐一分析這個異常處理器的構成和功能:

功能解析

  1. @RestControllerAdvice

    • 這個註解將類標記為全域性異常處理類,專用於 REST 控制器。它確保所有方法的返回結果都將自動序列化為 JSON 或其他 RESTful API 響應格式。

  2. @ExceptionHandler(Exception.class)

    • 這個註解表示該方法可以處理所有的 Exception 類及其子類的例項。這是一個非常廣泛的異常處理範圍,通常在沒有更具體的異常處理方法可用時使用。

  3. 異常處理邏輯

    • GlobalException: 特定型別的自定義異常,通常用於業務邏輯中的錯誤處理。這種異常包含一個 RespBeanEnum,用於定義具體的錯誤資訊和狀態碼。

    • BindException: 處理表單繫結時發生的異常(例如,Spring MVC 中使用 @Valid 註解驗證時)。這裡將錯誤資訊提取出來,並且構造一個具體的錯誤響應返回給前端。

  4. RespBean 類

    • 這是一個用於構建響應的輔助類,可能包含狀態碼、訊息和其他可能的資料載體。該類的設計應該支援錯誤和正常響應的構建。

  5. IsMobileValidator 類

    • 一個自定義的校驗器,用於校驗是否為有效的手機號。這裡體現了 Spring 的校驗框架如何與自定義校驗器結合使用。

    • 校驗邏輯依據 required 欄位確定是否強制校驗,以及 ValidatorUtil.isMobile 方法的實現來判定手機號碼的有效性。

特定的異常處理邏輯

這個例子中的處理邏輯更細化,並且針對不同型別的異常提供了定製化的響應。這是高效處理 RESTful API 中異常的一個典型示例,因為它不僅僅返回通用的錯誤資訊,還考慮到了業務邏輯中可能需要特別處理的異常型別(如 GlobalExceptionBindException)。

自定義校驗註解 @IsMobile

這個註解用於校驗手機號碼欄位,它使用 IsMobileValidator 作為其校驗器:

  • required 屬性決定是否必須校驗手機號碼。

  • message 預設錯誤訊息為 "手機號碼格式錯誤"。

使用此註解可以非常靈活地在實體或 DTO 類中指定需要校驗手機號的欄位,極大地增強了程式碼的可讀性和易用性。

總結

透過這樣的全域性異常處理策略,您的應用可以很容易地區分處理不同型別的業務邏輯錯誤和系統錯誤,同時向客戶端返回清晰、一致的錯誤響應格式。這對前後端分離的現代 Web 應用尤為重要,因為它允許前端開發者根據不同的錯誤程式碼和訊息進行相應的介面處理,提高使用者體驗。

自定義校驗註解的使用進一步提升了校驗邏輯的封裝性和複用性,使得手機號碼這樣的常見欄位校驗變得簡潔明瞭。這些技術的結合使用,不僅可以減少冗餘程式碼,還可以提升整個應用的穩定性和可維護性。


4 在典型的 Spring Boot 應用架構中,流程通常是從控制器層 (Controller) 接收請求,然後將業務邏輯處理委託給服務層 (Service),服務層執行具體的業務邏輯後返回資料給控制器層,最後控制器層返回響應給前端。這是 MVC (Model-View-Controller) 模式的一種實現,確保了層次間的職責清晰分明。

如何全域性異常處理改變了這個流程

全域性異常處理機制提供了一種集中處理異常的方式,這種方式確實有點“脫離”了正常的控制器->服務->控制器的響應流程。具體來說:

正常流程

  1. 控制器層:接收請求,呼叫服務層。

  2. 服務層:執行業務邏輯,處理資料。

  3. 控制器層:根據服務層的結果準備響應資料,返回給前端。

發生異常時的流程

  1. 控制器層:接收請求,呼叫服務層。

  2. 服務層:執行業務邏輯時可能丟擲異常。

  3. 全域性異常處理器:捕獲並處理異常,直接返回錯誤響應給前端。

在發生異常時,一旦異常被丟擲(無論是在控制器層還是服務層),它會被傳遞到呼叫棧直到遇到一個可以處理它的異常處理器。如果在控制器層或服務層內部沒有捕獲異常,全域性異常處理器 (@ControllerAdvice@RestControllerAdvice) 將捕獲這些異常。全域性異常處理器的存在是為了確保應用可以優雅地處理所有未被捕獲的異常,防止它們洩露到客戶端並給客戶端一個友好的錯誤反饋

全域性異常處理的優點

  • 集中管理錯誤處理邏輯:可以在一個地方處理所有異常,避免在每個控制器或服務中重複錯誤處理程式碼。

  • 保證錯誤響應的一致性:全域性異常處理可以確保應用程式中所有的錯誤響應都是一致的,這對前端開發者來說非常重要,因為他們可以根據統一的錯誤格式來處理錯誤情況。

  • 提高程式碼的可維護性:減少了每個方法內部處理異常的需要,程式碼更加簡潔和易於管理。

示例解釋

例如,如果服務層中的方法因為資料庫問題丟擲了一個 DataAccessException,在沒有全域性異常處理器的情況下,控制器需要捕獲這個異常並自己構造錯誤響應。但如果配置了全域性異常處理器,控制器層的程式碼就可以只關注於正常情況下的業務邏輯和資料流轉,異常情況由全域性處理器統一處理。

這樣的設計模式不僅提高了程式碼的整潔性,還增強了系統的健壯性,使得異常處理更加系統化和規範化。在實際應用中,這種全域性的錯誤處理機制被廣泛認為是構建健壯、易於維護的大型應用的最佳實踐之一。

5 全域性異常處理器之所以可以直接給前端返回資料資訊是因為有@ResponseBody註解的支援對嗎?

全域性異常處理器如何直接向前端返回資料,特別是在使用 @ControllerAdvice@RestControllerAdvice 時,@ResponseBody 註解的作用是什麼?

在使用 @ControllerAdvice@RestControllerAdvice 類定義全域性異常處理器時,@ResponseBody 註解起著關鍵的作用。這個註解確保方法的返回值直接作為響應體返回給客戶端,並且以 JSON 或其他媒體型別格式化,這通常是在構建 RESTful API 時所需要的。

核心程式碼示例

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public RespBean handleException(Exception e) {
return RespBean.error("發生錯誤: " + e.getMessage());
}
}

詳細解釋

@ResponseBody 的作用

  • 資料序列化@ResponseBody 註解告訴 Spring MVC 框架,方法的返回值應該直接寫入 HTTP 響應體中,而不是解釋為跳轉路徑或檢視名。這意味著返回的物件將被自動轉換成 JSON(或其他配置的格式,取決於內容協商策略)。

  • 內容型別處理:與 @ResponseBody 相關的還有 HttpMessageConverter 介面,Spring 使用這個介面的實現類來轉換控制器方法的返回值,使其變為客戶端可接受的形式,如 JSON、XML 等。

@RestControllerAdvice

  • 結合 @ResponseBody@RestControllerAdvice@ControllerAdvice@ResponseBody 的結合體。使用 @RestControllerAdvice 註解的類中定義的所有方法自動使用 @ResponseBody,所以當你用這個註解時,不需要再顯式新增 @ResponseBody

  • 適用場景:這種處理方式非常適合於構建 RESTful API,其中所有的響應都是資料響應,而不是檢視渲染。

@ResponseBody 的作用

  • 資料序列化

    • @ResponseBody 註解指導 Spring MVC 框架將方法的返回值直接寫入HTTP響應體中,而不是將返回值解釋為跳轉路徑或檢視名稱。

    • 這意味著返回的資料物件將自動轉換為 JSON(或根據內容協商策略配置的其他格式)。

  • 內容型別處理

    • HttpMessageConverter 介面的實現用於將控制器方法的返回值轉換成客戶端可接受的格式,如 JSON、XML 等。

    • Spring MVC 根據請求的Accept頭部資訊來選擇合適的HttpMessageConverter進行資料轉換。

@RestControllerAdvice 的優勢

  • 自動使用 @ResponseBody

    • @RestControllerAdvice@ControllerAdvice@ResponseBody 的組合,確保在該類中定義的所有方法自動應用 @ResponseBody

    • 使用這個註解後,不需要在每個方法上單獨新增 @ResponseBody,簡化了程式碼。

  • 適用場景

    • 這種處理方式特別適合於構建RESTful API,其中所有響應都是資料響應,這有助於前後端分離的架構實現。

工作流程

  1. 異常捕獲

    • 當應用中丟擲任何未被區域性異常處理器捕獲的 Exception 型別異常時,handleException 方法將被觸發。

  2. 異常處理

    • 方法內部構建了一個 RespBean 例項,其中包含錯誤資訊。這個例項根據方法的返回型別自動轉換為 JSON 格式的響應體。

  3. 資料返回

    • JSON 資料作為HTTP響應的主體直接返回給客戶端,HTTP狀態碼根據需要設定(例如,400 Bad Request, 500 Internal Server Error等)。

示例解釋

在此示例中,如果在應用執行過程中丟擲異常(如空指標異常、陣列越界等),全域性異常處理器中的 handleException 方法會處理這些異常。由於使用了 @RestControllerAdvice,無需額外使用 @ResponseBody,返回的 RespBean 物件會被自動轉換為 JSON 格式並響應給客戶端,使得錯誤處理流程既高效又易於管理。

結論

透過使用 @RestControllerAdvice@ResponseBody,全域性異常處理器不僅簡化了異常處理邏輯,而且提高了處理效率,確保了向前端返回的資料格式的一致性和準確性。這種機制極大地最佳化了現代Web應用架構的錯誤處理方式,使開發者可以更專注於業務邏輯的實現而非低階錯誤處理細節。這是構建高可維護、高可用Web應用的關鍵實踐之一。


6 您提出的問題非常關鍵,確實,全域性異常處理器的處理邏輯在某種程度上“脫離”了傳統的 Spring MVC 控制器到服務層再回到控制器的流程。全域性異常處理器(使用@ControllerAdvice@RestControllerAdvice)提供了一種集中處理異常的機制,這種機制允許開發者在異常發生時,直接從處理器跳轉到錯誤響應或錯誤頁面,而不需要再經過常規的服務層處理。這樣做的好處是提高了程式碼的複用性和清晰性,同時也保證了異常處理的一致性和可預測性。

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.ui.Model;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.WebRequest;
import org.springframework.http.HttpStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

// 處理通用異常
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(Exception ex, WebRequest request) {
return new ResponseEntity<>("發生異常: " + ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

// 處理特定異常,返回錯誤頁面
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String handleArithmeticException(Exception ex, Model model) {
model.addAttribute("error", "發生錯誤: " + ex.getLocalizedMessage());
return "error/global"; // 返回到指定的錯誤頁面
}
}

全域性異常處理流程與傳統流程的不同之處

1. 控制流的中斷和直接響應

在傳統的流程中:

  • 控制器層:接收請求,驗證引數,然後呼叫服務層。

  • 服務層:執行業務邏輯,運算元據庫等,然後將結果返回控制器層。

  • 控制器層:接收服務層結果,構建響應物件或檢視模型,返回給客戶端或轉發到檢視解析器。

在使用全域性異常處理器的流程中:

  • 異常發生:在控制器或服務層執行過程中,一旦發生異常(如ArithmeticExceptionNullPointerException),正常的處理流程被中斷。

  • 異常捕獲:全域性異常處理器捕獲這些異常。不需要在每個控制器或服務方法中手動捕獲。

  • 處理並響應:根據異常型別,異常處理器可能直接構造一個錯誤響應(如JSON格式的錯誤資訊)或準備錯誤檢視相關的資料並選擇錯誤頁面。這個過程不再回到原來的控制器方法。

2. 錯誤檢視的渲染而非資料處理

在您的GlobalExceptionHandler示例中,異常處理方法返回一個檢視名稱,這意味著:

  • 模型填充:異常資訊和可能的其他資料被填充到Model物件中。

  • 檢視選擇:返回的字串指向一個檢視(例如,"error/global"),這個檢視名稱被檢視解析器處理。

  • 檢視渲染:檢視解析器(如Thymeleaf或JSP)根據檢視名稱找到相應的模板,渲染成HTML,然後返回給客戶端。這個過程類似於請求轉發,但是實質上是在異常處理流程中直接處理,不透過常規的控制器響應流程。

3. 異常處理的優先順序和處理方式

  • 優先順序:全域性異常處理器具有高於預設錯誤頁面(如Tomcat的500.html)的處理優先順序。這意味著如果配置了全域性異常處理器,它將處理所有未被區域性處理器捕獲的異常。

  • 處理方式:處理器中的邏輯可以非常靈活,支援多種異常型別,可以根據不同異常型別返回不同的錯誤頁面或錯誤資訊。

詳細解答與工作原理

1. 使用 @ControllerAdvice

  • 作用@ControllerAdvice 註解允許您建立一個集中的異常處理器,該處理器會攔截所有控制器中丟擲的異常。它可以指定作用的包或控制器,如果不指定,則預設應用於所有的包和控制器。

  • 響應自動化:此類可以使用@ResponseBody註解,確保所有響應內容自動轉化為JSON格式,適合RESTful服務。

2. 使用 @ExceptionHandler

  • 功能:透過@ExceptionHandler指定哪些異常型別由哪個方法處理。可以定義多個方法處理不同型別的異常,提高處理的精確性和靈活性。

3. 錯誤檢視的渲染與直接響應

  • 錯誤檢視渲染

    • 模型填充:異常資訊被新增到Model物件,可在檢視中顯示詳細錯誤。

    • 檢視解析:返回的是檢視名(如"error/global"),Spring MVC 使用檢視解析器解析此名稱,找到相應檢視模板進行渲染,然後返回渲染後的HTML給客戶端。

    • 請求轉發特性:此處理過程類似於內部請求轉發,但不透過瀏覽器重定向,減少了客戶端的處理負擔。

  • 直接響應

    • 響應構建:對於非檢視響應,如RESTful API,異常處理方法直接構造並返回ResponseEntity物件,包含錯誤資訊和HTTP狀態碼,直接返回給客戶端。

4. 異常處理優先順序與方法

  • 優先順序管理:全域性異常處理器的處理優先順序高於預設的錯誤頁面(如Tomcat的500.html),確保應用可以自定義錯誤響應。

  • 靈活性:可以根據不同的異常型別,設計不同的處理邏輯和響應方式,包括錯誤頁面的返回或JSON資料的返回。

總結

全域性異常處理器提供了一種高效、集中的方式來處理應用中的異常,它透過中斷常規的控制流並直接響應來最佳化錯誤處理流程。這種機制不僅減少了程式碼的重複,提高了程式碼的複用性和清晰性,而且確保了異常處理的一致性和可預測性,特別適用於需要高可維護性和可擴充套件性的大型應用。透過使用@ControllerAdvice@RestControllerAdvice,開發者可以靈活地處理各種執行時異常,保證應用的穩定性和使用者的良好體驗。


7 全域性異常問題總結

1. 基礎全域性異常處理器的建立與配置

問題描述

如何在Spring Boot應用中建立並配置一個基礎的全域性異常處理器,用於捕獲和處理應用中丟擲的所有異常?

程式碼示例

@ControllerAdvice
public class BasicGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(Exception ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("發生異常: " + ex.getMessage());
}
}

詳細解答

詳細解答: 在Spring Boot中,使用@ControllerAdvice註解定義一個全域性異常處理類,可以包含一個或多個用@ExceptionHandler註解的方法,指定它們可以處理的異常型別。在上述程式碼中,handleAllExceptions方法被配置為處理所有型別的Exception,該方法返回包含錯誤資訊的HTTP響應。

@ExceptionHandler指定的異常型別表示應用丟擲這些型別的異常時,相應的方法將被呼叫。這種方式可以集中處理錯誤,提高程式碼的可維護性和響應結構的一致性。

2. 全域性異常處理器如何直接返回前端資料

問題描述

全域性異常處理器如何處理異常並直接向前端返回資料?這種方法與傳統的Controller到Service再到Controller的流程有何不同?

程式碼示例

@RestControllerAdvice
public class GlobalDataExceptionHandler {
@ExceptionHandler(value = {NullPointerException.class})
public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
return ResponseEntity
.badRequest()
.body("發生空指標異常: " + ex.getMessage());
}
}

詳細解答

使用@RestControllerAdvice(它結合了@ControllerAdvice@ResponseBody),全域性異常處理器的方法可以直接返回資料物件。Spring Boot會自動將這些物件序列化為JSON(或根據配置的其他格式)併傳送到前端。

這種方法允許異常處理方法直接構造並返回響應體,簡化了處理流程,提高了異常處理的效率和響應速度,同時也使得響應格式更加一致。

3. 使用@ResponseBody註解支援全域性異常處理器直接返回資料

問題描述

全域性異常處理器之所以可以直接給前端返回資料資訊,是因為有@ResponseBody註解的支援嗎?

詳細解答

是的,您的理解是正確的。@ResponseBody註解確保方法的返回值直接作為響應體返回給客戶端,並且以JSON或其他媒體型別格式化。這通常是在構建RESTful API時所需的。

@ResponseBody的作用主要是:

  • 資料序列化:告訴Spring框架,方法返回的物件應該被序列化為客戶端接受的格式(如JSON),並直接寫入HTTP響應體。

  • 內容型別處理:利用HttpMessageConverter介面的實現,Spring將返回值從Java物件轉換為如JSON或XML的格式。

@RestControllerAdvice中,@ResponseBody是隱含的,因此所有在@RestControllerAdvice註解的類中的方法都將自動使用@ResponseBody。這使得這些方法能夠直接返回資料,而無需

進行額外的配置。

問題4: 全域性異常處理的流程最佳化

問題描述: 全域性異常處理改變了傳統的Controller-Service-Controller響應流程,請問這是如何實現的?

詳細解答: 全域性異常處理器提供了一種集中處理異常的方式,這允許在異常發生時,直接從處理器跳轉到錯誤響應,而不需透過常規的服務層處理。這樣的設計最佳化了處理流程,提高了程式碼複用性與清晰度,同時保證了異常處理的一致性和可預測性。

異常處理流程與傳統流程不同之處:

  • 控制流中斷:傳統流程中,資料透過控制器層到服務層再返回控制器層。在使用全域性異常處理器的流程中,一旦發生異常,該異常會直接由全域性處理器捕獲並處理,流程中斷,直接返回錯誤響應。

  • 直接響應:異常處理器根據異常型別直接構造錯誤響應(如JSON格式的錯誤資訊),而不是返回到原控制器方法。

結論

透過集中管理異常處理,全域性異常處理器不僅提高了程式碼的可維護性和一致性,還最佳化了應用的錯誤響應過程,使其更加高效和使用者友好。這些處理器特別適合於構建大型和複雜的應用,可以顯著減輕單個控制器或服務元件的負擔,提高整體應用的錯誤處理能力。

相關文章