spring boot slf4j logback listener filter json aop http get scheduling tasks

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

客製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 ~


,

Related posts

Latest posts