您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)spring boot 如何統(tǒng)一處理Filter、Servlet中的異常信息,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
用戶權(quán)限校驗(yàn)
用戶操作的日志記錄
黑名單、白名單
等等…
可以使用過濾器對(duì)請求進(jìn)行預(yù)處理,預(yù)處理完畢之后,再執(zhí)行 chain.doFilter() 將程序放行。
自定義過濾器,只需要實(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() !
讓自定義的 Filter 被 Spring 的 IOC 容器管理,常用的實(shí)現(xiàn)方式有兩種,分別為:
在 TestFilter 類上添加 @WebFilter 注解,
然后在啟動(dòng)類上增加 @ServletComponentScan 注解,就可以了。
其中在 @WebFilter 注解上可以指定過濾器的名稱和匹配的 url 數(shù)組,如下圖所示:
這種方式雖然可以指定 filter 名稱和匹配的 url ,但是不能指定各 filter 之間的執(zhí)行順序。
通過 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是一個(gè)Java編寫的程序,此程序是基于http協(xié)議的,在服務(wù)器端(如Tomcat)運(yùn)行的,是按照servlet規(guī)范編寫的一個(gè)Java類。
客戶端發(fā)送請求至服務(wù)器端,服務(wù)器端將請求發(fā)送至servlet,servlet生成響應(yīng)內(nèi)容并將其傳給服務(wù)器。
處理客戶端的請求并將其結(jié)果發(fā)送到客戶端。
自定義 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)行請求處理返回。
HttpServletRequest 用來接收請求參數(shù),HttpServletResponse 用來返回請求結(jié)果。
某個(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));
}
String requestUri = req.getRequestURI();
String methodType = req.getMethod();
String queryString = req.getQueryString();
if (!StrUtil.isBlank(queryString)) {
log.info("請求行中的參數(shù)部分為: {}", queryString);
url = url + "?" + queryString;
}
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 "";
}
}
返回結(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();
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í)行。
現(xiàn)在我在 TestFilter 中,添加了一個(gè)必報(bào)異常的代碼,發(fā)現(xiàn)使用 @RestControllerAdvice + @ExceptionHandler 并不能捕獲該 filter 的異常。
其實(shí) @RestControllerAdvice + @ExceptionHandler 并非可以解決所有異常返回信息,它倒是能攔截 Controller 層的異常報(bào)錯(cuò),但是在 Filter、servlet 中的異常,使用以上注解就失效了,需要從別的方面進(jìn)行入手。
找了好久的資料,才知道怎么處理,所以也給大家分享一下。
我們都知道,當(dāng) spring boot 遇到錯(cuò)誤的時(shí)候,擁有自己的一套錯(cuò)誤提示邏輯,分為兩種情況:
頁面訪問形式
接口調(diào)用訪問形式
對(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 值必須為空。
創(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 。
/**
* 統(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;
}
}
/**
* 自定義異常類
*
* @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;
}
}
/**
* @author create17
* @date 2020/7/24
*/
public interface CodeEnum {
int getCode();
String getMsg();
}
/**
* 通過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;
}
}
/**
* 響應(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;
}
}
好了,到這里我們的全局異常就統(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ì)被攔截處理,如下圖所示:
參考博客:https://blog.csdn.net/Chen_RuiMin/article/details/104418904
不總結(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ò),可以把它分享出去讓更多的人看到。
免責(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)容。