溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息

發(fā)布時(shí)間:2021-12-02 15:28:06 來源:億速云 閱讀:179 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

一、過濾器 Filter

1、過濾器的作用或使用場景:
  • 用戶權(quán)限校驗(yàn)

  • 用戶操作的日志記錄

  • 黑名單、白名單

  • 等等…

可以使用過濾器對(duì)請求進(jìn)行預(yù)處理,預(yù)處理完畢之后,再執(zhí)行 chain.doFilter() 將程序放行。

 
2、自定義過濾器

自定義過濾器,只需要實(shí)現(xiàn) javax.servlet.Filter 接口即可。

public class TestFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(TestFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("test filter;");

        // 代表過濾通過,必須添加以下代碼,程序才可以繼續(xù)執(zhí)行
        chain.doFilter(request, response);
    }
}
 

Filter 接口有三個(gè)方法:init()、doFilter()、destroy()。

  • init():項(xiàng)目啟動(dòng)初始化的時(shí)候會(huì)被加載。

  • doFilter():過濾請求,預(yù)處理。

  • destroy():項(xiàng)目停止前,會(huì)執(zhí)行該方法。

其中 doFilter() 需要自己必須實(shí)現(xiàn),其余兩個(gè)是 default 的,可以不用實(shí)現(xiàn)。

注意:如果 Filter 要使請求繼續(xù)被處理,就一定要調(diào)用 chain.doFilter() !

 
3、配置 Filter 被 Spring 管理

讓自定義的 Filter 被 Spring 的 IOC 容器管理,常用的實(shí)現(xiàn)方式有兩種,分別為:

 
1)@WebFilter + @ServletComponentScan
  • 在 TestFilter 類上添加 @WebFilter 注解,

  • 然后在啟動(dòng)類上增加 @ServletComponentScan 注解,就可以了。

其中在 @WebFilter 注解上可以指定過濾器的名稱和匹配的 url 數(shù)組,如下圖所示:

spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息  

這種方式雖然可以指定 filter 名稱和匹配的 url ,但是不能指定各 filter 之間的執(zhí)行順序。

 
2)JavaConfig 配置

通過 JavaConfig 配置實(shí)現(xiàn) Filter 被 Spring 管理,推薦使用這種方式,該種方式可以指定各 filter 之間的執(zhí)行順序。setOrder 的值越小,越優(yōu)先執(zhí)行。

@Configuration
public class FilterConfiguration {

    @Bean
    public Filter testFilter() {
        return new TestFilter();
    }

    @Bean
    public FilterRegistrationBean registrationTestFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new DelegatingFilterProxy("testFilter"));
        registration.setName("testFilter");
        registration.addUrlPatterns("/v1/*");
        registration.addUrlPatterns("/v2/*");
        registration.setOrder(1);
        return registration;
    }

}
 

通過 JavaConfig 顯式配置 Filter ,功能強(qiáng)大,配置靈活。只需要把每個(gè)自定義的 Filter 聲明成 Bean 交給 Spring 管理即可,還可以設(shè)置匹配的 URL 、指定 Filter 的先后順序。

另外通過這種方式,還可以實(shí)現(xiàn)在自定義 filter 中自動(dòng)裝配一些對(duì)象 @Autowired 。

 

二、Servlet

 
1、Servlet 是什么:

servlet是一個(gè)Java編寫的程序,此程序是基于http協(xié)議的,在服務(wù)器端(如Tomcat)運(yùn)行的,是按照servlet規(guī)范編寫的一個(gè)Java類。

客戶端發(fā)送請求至服務(wù)器端,服務(wù)器端將請求發(fā)送至servlet,servlet生成響應(yīng)內(nèi)容并將其傳給服務(wù)器。

 
2、Servlet 的作用:

處理客戶端的請求并將其結(jié)果發(fā)送到客戶端。

 
3、自定義 Servlet

自定義 servlet 需要繼承一個(gè)抽象類,那就是 javax.servlet.http.HttpServlet。

然后在類上添加 @WebServlet 注解即可。

@WebServlet(name = "TestServlet", urlPatterns = {"/v1/*", "/v2/*"})
public class TestServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }
}
 

然后在啟動(dòng)類上增加 @ServletComponentScan 注解,自定義 Servlet 就完成了。

HttpServlet 中有很多方法,常用的還是重寫 service(HttpServletRequest req, HttpServletResponse resp) 方法,進(jìn)行請求處理返回。

 
4、HttpServletRequest 與 HttpServletResponse

HttpServletRequest 用來接收請求參數(shù),HttpServletResponse 用來返回請求結(jié)果。

 
1)獲取 header

某個(gè) header 值:

String clientid = req.getHeader("clientid");
 

遍歷所有 header 值:

Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
    String headerKey = headerNames.nextElement();
    log.info("{} : {}", headerKey, req.getHeader(headerKey));
}
   
2)獲取請求 uri
String requestUri = req.getRequestURI();
   
3)獲取請求類型
String methodType = req.getMethod();
   
4)獲取請求 params
String queryString = req.getQueryString();
if (!StrUtil.isBlank(queryString)) {
    log.info("請求行中的參數(shù)部分為: {}", queryString);
    url = url + "?" + queryString;
}
   
5)獲取請求 body
private String getBody(HttpServletRequest request) {
    //獲取body數(shù)據(jù)
    StringBuilder sb = null;
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String line = null;
        sb = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    } catch (IOException e) {
        log.error("獲取請求體異常:", e);
        return "";
    }
}
   
6)組裝返回結(jié)果

返回結(jié)果是用 HttpServletResponse 來組裝。如下述代碼所示:

resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = resp.getWriter();
printWriter.append(JSON.toJSONString(resultObj, SerializerFeature.WriteMapNullValue));
printWriter.close();
   

三、Filter 與 Servlet 的執(zhí)行順序

filter1 -> filter2 -> servlet,  之后 servlet 處理完,再回傳到 filter2 -> filter1 。

  • 如果 servlet 和 filter 都有 response 返回,返回到前端的是 servlet 的 response。

  • 如果 servlet 中沒有 response 返回,filter 中有 response 返回。這時(shí) filter 的 response 有效,返回到前端的是 filter 的 response。

filter 到 filter 或 servlet ,是通過 chain.doFilter(request, response); 這條命令來進(jìn)行通過的。當(dāng)從 servlet 中返回到 filter 時(shí),chain.doFilter(request, response); 后面的代碼會(huì)繼續(xù)被執(zhí)行。

 

四、Filter、Servlet 的全局異常統(tǒng)一處理

現(xiàn)在我在 TestFilter 中,添加了一個(gè)必報(bào)異常的代碼,發(fā)現(xiàn)使用 @RestControllerAdvice + @ExceptionHandler 并不能捕獲該 filter 的異常。

其實(shí) @RestControllerAdvice + @ExceptionHandler 并非可以解決所有異常返回信息,它倒是能攔截 Controller 層的異常報(bào)錯(cuò),但是在 Filter、servlet 中的異常,使用以上注解就失效了,需要從別的方面進(jìn)行入手。

找了好久的資料,才知道怎么處理,所以也給大家分享一下。

 
1、spring boot 錯(cuò)誤邏輯

我們都知道,當(dāng) spring boot 遇到錯(cuò)誤的時(shí)候,擁有自己的一套錯(cuò)誤提示邏輯,分為兩種情況:

  • 頁面訪問形式

spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息  
  • 接口調(diào)用訪問形式

spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息  
 
2、繼承 BasicErrorController ,重寫 error() 方法

對(duì)于接口調(diào)用訪問的形式來說,我們可以來繼承 BasicErrorController 類,重寫 error() 方法,在 error() 方法里面對(duì)全局異常進(jìn)行統(tǒng)一處理。

通過觀察 BasicErrorController 可以發(fā)現(xiàn),它處理的就是 /error 請求。我們繼承 BasicErrorController 之后,就只需要重新組裝 /error 的請求返回即可。

代碼實(shí)現(xiàn)如下:

@RestController
public class ErrorController extends BasicErrorController {

    private static final Logger log = LoggerFactory.getLogger(ErrorController.class);

    public ErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    /**
     * produces 設(shè)置返回的數(shù)據(jù)類型:application/json
     *
     * @param request 請求
     * @return 自定義的返回實(shí)體類
     */
    @Override
    @RequestMapping(value = "", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        // 獲取錯(cuò)誤信息
        String message = body.get("message").toString();
        int code = EnumUtil.getCodeByMsg(message, ResultEnum.class);

        HttpStatus httpStatus;
        if (code == 500) {
            // 服務(wù)端異常,狀態(tài)碼為500
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        } else {
            // 其余異常(手動(dòng)throw)為邏輯校驗(yàn),狀態(tài)碼為200
            httpStatus = HttpStatus.OK;
        }
        return new ResponseEntity(Result.failed(code, message), httpStatus);
    }

}
 

其中注解 @RestController 是必填的,@RequestMapping 的 value 值必須為空。

 
3、全局異常統(tǒng)一處理邏輯
 
核心:
  • 創(chuàng)建 ResultEnum 枚舉類,用來存儲(chǔ)多個(gè)異常信息( code 和 msg )。

  • 創(chuàng)建自定義異常類 CustomException,讓其可以接收 ResultEnum 枚舉類內(nèi)容。方便程序 throw 。

  • 創(chuàng)建 Result 類,用于封裝返回結(jié)果到前端。

  • 重寫 error() 方法。

在 error() 方法中,我們可以獲取到原 /error 請求的返回結(jié)果,然后獲取 message 報(bào)錯(cuò)信息。然后根據(jù) message 來獲取枚舉類與之對(duì)應(yīng)的 code 值,然后將 code 和 message 填充到 Result 主體,返回到前端。

又對(duì) HttpStatus 請求狀態(tài)碼進(jìn)行了判斷,當(dāng)手動(dòng) throw 拋出的異常,請求狀態(tài)碼為 200;如果是程序預(yù)料之外的異常,沒有處理的,請求狀態(tài)碼就是 500 。

 
ResultEnum 枚舉類:
/**
 * 統(tǒng)一管理返回?cái)?shù)據(jù)結(jié)果code和message,返回結(jié)果枚舉
 *
 * @author create17
 * @date 2020/5/13
 */
public enum ResultEnum implements CodeEnum {

    /**
     * clientid expired
     */
    ZERO_EXCEPTION(1052, "/ by zero")
    ;

    private int code;

    private String msg;

    ResultEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public int getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }

}
   
CustomException 自定義異常類:
/**
 * 自定義異常類
 *
 * @author create17
 * @date 2020/5/13
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class CustomException extends RuntimeException {

    private int code;

    private String msg;

    public CustomException(ResultEnum resultEnum) {
        // 自定義錯(cuò)誤棧中顯示的message
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getMsg();
    }

    public CustomException(int code, String msg) {
        // 自定義錯(cuò)誤棧中顯示的message
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}
   
CodeEnum 接口:
/**
 * @author create17
 * @date 2020/7/24
 */
public interface CodeEnum {

    int getCode();

    String getMsg();

}
   
EnumUtil 工具類:
/**
 * 通過code找到msg, 通過msg找到code
 * 
 * @author create17
 * @date 2020/7/24
 */
public class EnumUtil {

    public static <T extends CodeEnum> String getMsgByCode(Integer code, Class<T> t){
        for(T item: t.getEnumConstants()){
            if(item.getCode() == code){
                return item.getMsg();
            }
        }
        return "";
    }

    public static <T extends CodeEnum> Integer getCodeByMsg(String msg, Class<T> t){
        for(T item: t.getEnumConstants()){
            if(StrUtil.equals(item.getMsg(),msg)){
                return item.getCode();
            }
        }
        return 500;
    }

}
   
Result 類:
/**
 * 響應(yīng)信息主體
 *
 * @author create17
 * @date 2020/5/28
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final int SUCCESS_CODE = 0;

    public static final int FAIL_CODE = 1;

    public static final String SUCCESS_MSG = "success";

    public static final String FAIL_MSG = "error";

    /**
     * 返回標(biāo)記:成功標(biāo)記=0,失敗標(biāo)記=1
     */
    private int code;

    /**
     * 返回信息
     */
//    @JsonProperty("message")
    private String message;

    /**
     * 數(shù)據(jù)
     */
//    @JsonProperty("results")
    private T results;

    public static <T> Result<T> ok() {
        return restResult(null, SUCCESS_CODE, SUCCESS_MSG);
    }

    public static <T> Result<T> ok(T data) {
        return restResult(data, SUCCESS_CODE, SUCCESS_MSG);
    }

    public static <T> Result<T> ok(T data, String msg) {
        return restResult(data, SUCCESS_CODE, msg);
    }

    public static <T> Result<T> failed() {
        return restResult(null, FAIL_CODE, FAIL_MSG);
    }

    public static <T> Result<T> failed(String msg) {
        return restResult(null, FAIL_CODE, msg);
    }

    public static <T> Result<T> failed(int code, String msg) {
        return restResult(null, code, msg);
    }

    public static <T> Result<T> failed(ResultEnum resultEnum) {
        return restResult(null, resultEnum.getCode(), resultEnum.getMsg());
    }

    private static <T> Result<T> restResult(T data, int code, String msg) {
        Result<T> apiResult = new Result<>();
        apiResult.setCode(code);
        apiResult.setResults(data);
        apiResult.setMessage(msg);
        return apiResult;
    }
}
   
4、測試

好了,到這里我們的全局異常就統(tǒng)一處理完了,filter 和 servlet 的異常不出意外的話,都會(huì)經(jīng)過 ErrorController 類。我先現(xiàn)在測試一下。

在 TestFilter 中,添加以下代碼:

try {
    int aa = 1/0;
} catch (Exception e) {
    throw new CustomException(ResultEnum.ZERO_EXCEPTION);
}
 

不出意外的話,異常會(huì)被攔截處理,如下圖所示:

spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息  

參考博客:https://blog.csdn.net/Chen_RuiMin/article/details/104418904

 

五、總結(jié)

不總結(jié)的文章不是好文章,我們最后來總結(jié)一下。

首先是講解了過濾器 Filter 的使用場景,實(shí)現(xiàn)方式,然后提供了兩種 Filter 被 Spring 管理的方法,其中特別推薦使用 JavaConfig 配置使 Filter 被 Spring 管理,因?yàn)檫@樣不僅可以指定多個(gè) Filter 之間的執(zhí)行順序,還能實(shí)現(xiàn)在 Filter 里面自動(dòng)裝配一些對(duì)象。

第二又介紹了 Servlet 的實(shí)現(xiàn)方式,HttpServletRequest 與 HttpServletResponse 的使用。

第三是概述了一下 Filter 與 Servlet 的執(zhí)行順序。

第四是文章中最想分享的地方,那就是如何統(tǒng)一處理 Filter 與 Servlet 的全局異常,嘗試了很多方法,最終認(rèn)為繼承 BasicErrorController,重寫 error() 方法是挺好的實(shí)現(xiàn)方式。

關(guān)于spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI