相信許多人都有遇過例外處理的事件,但又不想要讓內部核心的錯誤資訊呈現在前端上,僅記錄在LOG檔中,並呈現一段漂漂亮亮的訊息呈現至前端,小編今天將延續昨天的架構再多上一層錯誤分析處理給大家進行學習,為了防止駭客的破壞,為了守護系統平台的和平,貫徹錯誤防止與守護的力量,我是小編威斯丁,現在帶領你們一起進去REST API最佳的建議控制器註解模式(@RestControllerAdvice)的世界,可提供開發者進行告知使用者哪些是必備條件,帶領你快速達到防呆效用及提升系統的保障性質囉。
Spring 框架中提供了錯誤例外回覆的功能,會在系統啟動中,會啟動一個SpringApplicationRunListeners,進行一個錯誤分析器的觀察器,最終都會都過HandlerExceptionResolver元件進行分析處理,若沒有預設此例外處理控制器模式註解(@ControllerAdvice),則會統一回覆一個JSON格式,範例請參照下方格式。
{
"timestamp": "2021-09-17T17:54:26.677+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/sea/v1/taiwan/update"
}
錯誤訊息可能會列出所有錯誤的程式碼片段,故Spring框架為優雅及客製化錯誤訊息,為加強化服務導向架構(SOA,Service-Oriented Architecture)開發結構,則提供了一個註解模式元件稱為例外處理控制器(@ControllerAdvice),我們通過以下範例來解釋此註解模式運用。
範例一、配置例外處理程序(@ExceptionHandler),並透過受限制類別(assignableType)進行限制支援的控制器類別。
@RestControllerAdvice(assignableTypes= {ProductController.class})
public class SeaControllerAdvice {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseData resourceNotFound(ResourceNotFoundException ex) {
return new ResponseData().setCode(ResponseStatusEnum.RESOURCES_NOT_FOUND.getCode())
.setStatus(ResponseStatusEnum.RESOURCES_NOT_FOUND.getStatus())
.setData(ex.toString());
}
@ExceptionHandler(SeaFoodRetailerGenericException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
ResponseData retailerGenericException(SeaFoodRetailerGenericException ex) {
return new ResponseData().setCode(ResponseStatusEnum.SERVICE_INTERNAL_ERROR.getCode())
.setStatus(ResponseStatusEnum.SERVICE_INTERNAL_ERROR.getStatus())
.setData(ex.toString());
}
}
public interface SeaFoodRetailerService {
.....
.....
.....
default void validateNullId(SeaFood entity) throws SeaFoodRetailerGenericException {
if (entity.getId() ==null )
throw new SeaFoodRetailerGenericException("Sea Food Id is REQUIRED ! ");
}
default void validateResuouceNotFound(SeaFood bodyEntity) throws ResourceNotFoundException {
if ( !this.listSeaFoodProducts()
.stream()
.anyMatch(seaFood -> seaFood.getId().equalsIgnoreCase(bodyEntity.getId())) )
throw new ResourceNotFoundException();
}
}
範例一 測試結果,可看出taiwan API有支援錯誤回覆格式,China API未支援。
// {host:port}/sea/v1/taiwan/update
{
"code": 503,
"status": "AH ! Oops ! Oops ! Sea Food retailer service is BROKEN ! BROKEN ! BROKEN !",
"data": "sw.spring.sample.exception.SeaFoodRetailerGenericException: Sea Food Id is REQUIRED ! "
}
// {host:port}/sea/v1/china/update
{
"timestamp": "2021-09-17T18:58:38.965+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/sea/v1/china/update"
}
範例二、透過basePackages進行配置專案路徑,限制哪些package 路徑下才有支援錯誤解析,範例為整個soa目錄下控制器受@ControllerAdvice保護。
@RestControllerAdvice(assignableTypes= {ProductController.class},basePackages = {"sw.spring.sample.services" })
public class SeaControllerAdvice {
...
...
...
}
範例二、可看出原先未支援的/sea/v1/china/update已呈現我們客製化的錯誤訊息。
{
"code": 503,
"status": "AH ! Oops ! Oops ! Sea Food retailer service is BROKEN ! BROKEN ! BROKEN !",
"data": "sw.spring.sample.exception.SeaFoodRetailerGenericException: Sea Food Id is REQUIRED ! "
}
透過以上的多元化的例外處理控制器可看出不僅可彈性配置限制範圍,亦可支援客製化多種錯誤處理回覆格式。
由下圖可看出,每個系統服務的入口點為ApplicationRun此啟動點,在此執行緒端建立一個監聽器實體(SpringApplicationRunListeners)進行監聽各種錯誤訊息,其中所有錯誤會透過一個推播器(EventPublishingRunListener)進行發送例外事件,這是一個多點事件發送器(MulticastEvent),為一種觀察設計者模式(Observer Pattern),當例外事件發送出去後,最終透過DispatcherServlet中進行分析與處理各式例外事件(HandlerExceptionResolver)。
圖一、Spring 錯誤監聽方法流程圖
圖二流程圖為例外事件控制器在Restful API上的運作流程,首先須注意,若有配置例外事件控制器(ControllerAdvice),請開發者別再配置異常處理程序(ExceptionHandler),不然API則會轉出預設的錯誤例外JSON資訊,異常處理程序(@ExceptionHandler)與回覆狀態(@ResponseStatus)需一同配置於例外事件控制器(ControllerAdvice)中,才能夠接取開發者所配置的客製化錯誤回覆訊息,不然皆產生預設的錯誤回覆格式喔。
圖二、Spring 控制器例外事件回覆訊息流程圖
以上為Spring 核心例外處理事件流程,提供各外開發者做參考。
java sample source - spring-sample-controller
PostMan API Trigger sample source
Complete Guide to Exception Handling in Spring Boot
後面我們會一直延續這個範例成長成一個系統。小編在前端小有研究,因Spring有提供一個GUI監測介面採用Angular開發,不過使用起來不是很順暢,而且配置太麻煩,不是小編想要的簡單概念,故最後小編選擇整合入一個Vue的前端框架進行開發,搭配Vuetify GUI設計框架,這套小編目前覺得用起來還不錯,比Creative-Tim、Element-UI或Framework-7好用的多,是2018/02才開發出的穩定版本,方便於轉導出我們所概述的領域驅動設計(DDD,Domain Driven Design)服務概念,為了在明年的主題留下一顆小彩蛋,有興趣的全端開發者們,歡迎一起來探討,小編哪邊說得不好請多多來指導小編。