![](http://static.javashuo.com/static/loading.gif)
每個成功人士的背後,一定曾經作出過勇敢而又孤獨的決定。前端
放棄不難,但堅持很酷~java
版本:web
springboot:2.2.7spring
1、過濾器 Filter
一、過濾器的做用或使用場景:
用戶權限校驗json
用戶操做的日誌記錄後端
黑名單、白名單api
等等…數組
可使用過濾器對請求進行預處理,預處理完畢以後,再執行 chain.doFilter() 將程序放行。springboot
二、自定義過濾器
自定義過濾器,只須要實現 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;");
// 表明過濾經過,必須添加如下代碼,程序才能夠繼續執行
chain.doFilter(request, response);
}
}
Filter 接口有三個方法:init()、doFilter()、destroy()。
init():項目啓動初始化的時候會被加載。
doFilter():過濾請求,預處理。
destroy():項目中止前,會執行該方法。
其中 doFilter() 須要本身必須實現,其他兩個是 default 的,能夠不用實現。
注意:若是 Filter 要使請求繼續被處理,就必定要調用 chain.doFilter() !
三、配置 Filter 被 Spring 管理
讓自定義的 Filter 被 Spring 的 IOC 容器管理,經常使用的實現方式有兩種,分別爲:
1)@WebFilter + @ServletComponentScan
在 TestFilter 類上添加 @WebFilter 註解,
而後在啓動類上增長 @ServletComponentScan 註解,就能夠了。
其中在 @WebFilter 註解上能夠指定過濾器的名稱和匹配的 url 數組,以下圖所示:
![](http://static.javashuo.com/static/loading.gif)
這種方式雖然能夠指定 filter 名稱和匹配的 url ,可是不能指定各 filter 之間的執行順序。
2)JavaConfig 配置
經過 JavaConfig 配置實現 Filter 被 Spring 管理,推薦使用這種方式,該種方式能夠指定各 filter 之間的執行順序。setOrder 的值越小,越優先執行。
@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 ,功能強大,配置靈活。只須要把每一個自定義的 Filter 聲明成 Bean 交給 Spring 管理便可,還能夠設置匹配的 URL 、指定 Filter 的前後順序。
另外經過這種方式,還能夠實如今自定義 filter 中自動裝配一些對象 @Autowired 。
2、Servlet
一、Servlet 是什麼:
servlet是一個Java編寫的程序,此程序是基於http協議的,在服務器端(如Tomcat)運行的,是按照servlet規範編寫的一個Java類。
客戶端發送請求至服務器端,服務器端將請求發送至servlet,servlet生成響應內容並將其傳給服務器。
二、Servlet 的做用:
處理客戶端的請求並將其結果發送到客戶端。
三、自定義 Servlet
自定義 servlet 須要繼承一個抽象類,那就是 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);
}
}
而後在啓動類上增長 @ServletComponentScan 註解,自定義 Servlet 就完成了。
HttpServlet 中有不少方法,經常使用的仍是重寫 service(HttpServletRequest req, HttpServletResponse resp) 方法,進行請求處理返回。
四、HttpServletRequest 與 HttpServletResponse
HttpServletRequest 用來接收請求參數,HttpServletResponse 用來返回請求結果。
1)獲取 header
某個 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("請求行中的參數部分爲: {}", queryString);
url = url + "?" + queryString;
}
5)獲取請求 body
private String getBody(HttpServletRequest request) {
//獲取body數據
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)組裝返回結果
返回結果是用 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();
3、Filter 與 Servlet 的執行順序
filter1 -> filter2 -> servlet, 以後 servlet 處理完,再回傳到 filter2 -> filter1 。
若是 servlet 和 filter 都有 response 返回,返回到前端的是 servlet 的 response。
若是 servlet 中沒有 response 返回,filter 中有 response 返回。這時 filter 的 response 有效,返回到前端的是 filter 的 response。
filter 到 filter 或 servlet ,是經過 chain.doFilter(request, response); 這條命令來進行經過的。當從 servlet 中返回到 filter 時,chain.doFilter(request, response); 後面的代碼會繼續被執行。
4、Filter、Servlet 的全局異常統一處理
如今我在 TestFilter 中,添加了一個必報異常的代碼,發現使用 @RestControllerAdvice + @ExceptionHandler 並不能捕獲該 filter 的異常。
其實 @RestControllerAdvice + @ExceptionHandler 並不是能夠解決全部異常返回信息,它卻是能攔截 Controller 層的異常報錯,可是在 Filter、servlet 中的異常,使用以上註解就失效了,須要從別的方面進行入手。
找了很久的資料,才知道怎麼處理,因此也給你們分享一下。
一、spring boot 錯誤邏輯
咱們都知道,當 spring boot 遇到錯誤的時候,擁有本身的一套錯誤提示邏輯,分爲兩種狀況:
頁面訪問形式
![](http://static.javashuo.com/static/loading.gif)
接口調用訪問形式
![](http://static.javashuo.com/static/loading.gif)
二、繼承 BasicErrorController ,重寫 error() 方法
對於接口調用訪問的形式來講,咱們能夠來繼承 BasicErrorController 類,重寫 error() 方法,在 error() 方法裏面對全局異常進行統一處理。
經過觀察 BasicErrorController 能夠發現,它處理的就是 /error 請求。咱們繼承 BasicErrorController 以後,就只須要從新組裝 /error 的請求返回便可。
代碼實現以下:
@RestController
public class ErrorController extends BasicErrorController {
private static final Logger log = LoggerFactory.getLogger(ErrorController.class);
public ErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
/**
* produces 設置返回的數據類型:application/json
*
* @param request 請求
* @return 自定義的返回實體類
*/
@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);
// 獲取錯誤信息
String message = body.get("message").toString();
int code = EnumUtil.getCodeByMsg(message, ResultEnum.class);
HttpStatus httpStatus;
if (code == 500) {
// 服務端異常,狀態碼爲500
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
} else {
// 其他異常(手動throw)爲邏輯校驗,狀態碼爲200
httpStatus = HttpStatus.OK;
}
return new ResponseEntity(Result.failed(code, message), httpStatus);
}
}
其中註解 @RestController 是必填的,@RequestMapping 的 value 值必須爲空。
三、全局異常統一處理邏輯
核心:
建立 ResultEnum 枚舉類,用來存儲多個異常信息( code 和 msg )。
建立自定義異常類 CustomException,讓其能夠接收 ResultEnum 枚舉類內容。方便程序 throw 。
建立 Result 類,用於封裝返回結果到前端。
重寫 error() 方法。
在 error() 方法中,咱們能夠獲取到原 /error 請求的返回結果,而後獲取 message 報錯信息。而後根據 message 來獲取枚舉類與之對應的 code 值,而後將 code 和 message 填充到 Result 主體,返回到前端。
又對 HttpStatus 請求狀態碼進行了判斷,當手動 throw 拋出的異常,請求狀態碼爲 200;若是是程序預料以外的異常,沒有處理的,請求狀態碼就是 500 。
ResultEnum 枚舉類:
/**
* 統一管理返回數據結果code和message,返回結果枚舉
*
* @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) {
// 自定義錯誤棧中顯示的message
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
this.msg = resultEnum.getMsg();
}
public CustomException(int code, String msg) {
// 自定義錯誤棧中顯示的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 類:
/**
* 響應信息主體
*
* @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";
/**
* 返回標記:成功標記=0,失敗標記=1
*/
private int code;
/**
* 返回信息
*/
// @JsonProperty("message")
private String message;
/**
* 數據
*/
// @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;
}
}
四、測試
好了,到這裏咱們的全局異常就統一處理完了,filter 和 servlet 的異常不出意外的話,都會通過 ErrorController 類。我先如今測試一下。
在 TestFilter 中,添加如下代碼:
try {
int aa = 1/0;
} catch (Exception e) {
throw new CustomException(ResultEnum.ZERO_EXCEPTION);
}
不出意外的話,異常會被攔截處理,以下圖所示:
![](http://static.javashuo.com/static/loading.gif)
參考博客:https://blog.csdn.net/Chen_RuiMin/article/details/104418904
5、總結
不總結的文章不是好文章,咱們最後來總結一下。
首先是講解了過濾器 Filter 的使用場景,實現方式,而後提供了兩種 Filter 被 Spring 管理的方法,其中特別推薦使用 JavaConfig 配置使 Filter 被 Spring 管理,由於這樣不只能夠指定多個 Filter 之間的執行順序,還能實如今 Filter 裏面自動裝配一些對象。
第二又介紹了 Servlet 的實現方式,HttpServletRequest 與 HttpServletResponse 的使用。
第三是概述了一下 Filter 與 Servlet 的執行順序。
第四是文章中最想分享的地方,那就是如何統一處理 Filter 與 Servlet 的全局異常,嘗試了不少方法,最終認爲繼承 BasicErrorController,重寫 error() 方法是挺好的實現方式,因此就趕快分享給你們嘍~
點關注,不迷路
好了各位,以上就是這篇文章的所有內容了,能看到這裏的人呀,都是人才。
也感謝各位的支持和承認,給予我最大的創做動力吧,咱們下篇文章見!
若是本篇博客有任何錯誤,請批評指教,不勝感激 !
若是這篇文章對你有所啓發,點贊、轉發都是一種支持!
朕已閱
本文分享自微信公衆號 - 大數據實戰演練(gh_f942bfc92d26)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。