Spring Boot EP 13:Exception的全域/區域與客製化處理
全域Exception處理
步驟1:在”STOCKMARKET/src/main/java/stockmarket/jovepater/com/stockmarket/”建立名為Handlers的資料夾,並新增GlobalExceptionHandler.java檔案。
步驟2:GlobalExceptionHandler.java內容如下:
package stockmarket.jovepater.com.stockmarket.Handlers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import stockmarket.jovepater.com.stockmarket.Classes.RspBody;
import stockmarket.jovepater.com.stockmarket.Commons.FormatConverter;
// 執行順序1
@Order(1)
// Advice也是一種AOP的實現
@ControllerAdvice
public class GlobalExceptionHandler extends RuntimeException {
// 註記將返回(return)的資料放入HTTP Response Body中
@ResponseBody
// 註記這個例外處理的範圍是所有的Exception.class,所有例外都會歸在Exception類,代表全域的意思
@ExceptionHandler(Exception.class)
// 將外部的Exception內容透過參數傳進來
public ResponseEntity<Object> GlobalExceotion(Exception exception) {
Logger myLogger = LoggerFactory.getLogger(exception.getClass().getName());
myLogger.error("Global Exception Handler. Message: {}", exception.getMessage());
// 組裝Response Body
String jsonRspBody = new FormatConverter()
.Object2JsonString(new RspBody("9999", "Global Exception Handler", exception.getMessage()));
// 設定Response的Header資訊
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
// 回覆一個Response實體
return new ResponseEntity<>(jsonRspBody, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
步驟3:在HelloController中,寫int x = 5 / 0;,讓程式發生錯誤,觀查日誌如下:
2022-02-08 20:51:36.946 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Filters.HttpTransatcionLoggingFilter: Request: GET /api/hello , Msg: Auth OK.
2022-02-08 20:51:36.946 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Filters.HttpTransatcionLoggingFilter: Request: GET /api/hello , Msg: Content Type Checked.
2022-02-08 20:51:36.946 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Filters.HttpTransatcionLoggingFilter: Request: GET /api/hello , from: 127.0.0.1
2022-02-08 20:51:36.985 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
2022-02-08 20:51:36.985 WARN [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
2022-02-08 20:51:36.985 ERROR [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
# 由Filter攔截的Exception
2022-02-08 20:51:36.986 ERROR [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: API Finished by Exception: / by zero
2022-02-08 20:51:36.986 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: Time used: 14 ms
2022-02-08 20:51:37.025 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController:
Controller Logging Message:
---------------------------------------------------
Session ID: 542F388B6500BDD3E74E945D311F2D97
Protocol: HTTP/1.1
Method: GET
Url: http://127.0.0.1:8080/api/hello
Agent: PostmanRuntime/7.28.4
Remote: 127.0.0.1:55071
Class Name: stockmarket.jovepater.com.stockmarket.Controllers.HelloController
Class Method: Hello
# 由Global Exception Handler所攔截處理
2022-02-08 20:51:37.027 ERROR [http-nio-8080-exec-1] java.lang.ArithmeticException: Global Exception Handler. Message: / by zero
2022-02-08 20:51:37.081 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Filters.HttpTransatcionLoggingFilter: Response: 500 /api/hello
步驟4:可以看到Response的HTTP Body與預設不同,改由客製的內容所取代。
客製Exception處理
步驟1:在Handler資料夾新增ExceptionCollecter.java,這個類別用於定義Exception的內容。
package stockmarket.jovepater.com.stockmarket.Handlers;
public class ExceptionCollecter extends RuntimeException {
// 自訂例外代碼
private String strExceptionCode;
// 自訂例外訊息
private String strExceptionMessage;
public ExceptionCollecter(String strExceptionCode, String strExceptionMessage) {
this.strExceptionCode = strExceptionCode;
this.strExceptionMessage = strExceptionMessage;
}
// getters and setters
}
步驟2:在Handler資料夾新增SpecificExceptionHandler.java,用於處理客製的Exception事件。
package stockmarket.jovepater.com.stockmarket.Handlers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import stockmarket.jovepater.com.stockmarket.Classes.RspBody;
import stockmarket.jovepater.com.stockmarket.Commons.FormatConverter;
// 執行順序0
@Order(0)
@ControllerAdvice
public class SpecificExceptionHandler {
@ResponseBody
// 僅包含自訂的Exception
@ExceptionHandler(ExceptionCollecter.class)
public ResponseEntity<Object> ColletcerException(ExceptionCollecter exception) {
Logger myLogger = LoggerFactory.getLogger(exception.getClass().getName());
myLogger.error("Specific Exception Handler. Message: {}",
exception.getExceptionMessage());
String jsonRspBody = new FormatConverter()
.Object2JsonString(new RspBody(exception.getExceptionCode(), "Specific Exception Handler",
exception.getExceptionMessage()));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<>(jsonRspBody, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
步驟3:在HelloController中呼叫ExceptionCollecter(String, String)方法來觸發客製的Exception。
throw new ExceptionCollecter("0909", "test exception");
步驟4:可以看到發生Exception時,日誌顯示被客製的Handler攔截處理了。
2022-02-08 21:06:47.481 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
2022-02-08 21:06:47.481 WARN [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
2022-02-08 21:06:47.481 ERROR [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: This is Hello API.
# Filter無法辨識客製化的Exception,但仍會被攔截到,只是資訊解析不出來
2022-02-08 21:06:47.482 ERROR [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: API Finished by Exception: null
2022-02-08 21:06:47.482 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController: Time used: 13 ms
2022-02-08 21:06:47.509 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Controllers.HelloController:
Controller Logging Message:
---------------------------------------------------
Session ID: 5DBF0F0BB27806A23CD668F6229A575E
Protocol: HTTP/1.1
Method: GET
Url: http://127.0.0.1:8080/api/hello
Agent: PostmanRuntime/7.28.4
Remote: 127.0.0.1:55121
Class Name: stockmarket.jovepater.com.stockmarket.Controllers.HelloController
Class Method: Hello
# 被客製的Handler攔截處理了
2022-02-08 21:06:47.510 ERROR [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Handlers.ExceptionCollecter: Specific Exception Handler. Message: test exception
2022-02-08 21:06:47.568 INFO [http-nio-8080-exec-1] stockmarket.jovepater.com.stockmarket.Filters.HttpTransatcionLoggingFilter: Response: 500 /api/hello
步驟5:可以看到Response的HTTP Body已經是由Specific Exception Handler來回應。
提醒
基本上Global Exception Handler是一定會被觸發的,因為就算客製的Exception也算是Exception,所以使用指定@Order()的方式來設定觸發順序,且記,Global Exception Handler要放在最後執行,其餘的客製Exception Handler要優先執行,直接回覆並終止繼續執行下去,這樣才能不讓Global Exception Handler不被執行到。
Spring Boot Exception更詳細的說明:Exception Handling in Spring MVC
~ END ~