SpringBoot系列教程web篇之自定義異常處理HandlerExceptionResolver
關於Web應用的全域性異常處理,上一篇介紹了ControllerAdvice
結合@ExceptionHandler
的方式來實現web應用的全域性異常管理;
本篇博文則帶來另外一種並不常見的使用方式,通過實現自定義的HandlerExceptionResolver
,來處理異常狀態
上篇博文連結: SpringBoot系列教程web篇之全域性異常處理 本篇原文: SpringBoot系列教程web篇之自定義異常處理HandlerExceptionResolver
I. 環境搭建
首先得搭建一個web應用才有可能繼續後續的測試,藉助SpringBoot搭建一個web應用屬於比較簡單的活;
建立一個maven專案,pom檔案如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7</version>
<relativePath/> <!-- lookup parent from update -->
</parent>
<properties>
<project.build.sourceEncoding >UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
複製程式碼
II. HandlerExceptionResolver
1. 自定義異常處理
HandlerExceptionResolver
顧名思義,就是處理異常的類,介面就一個方法,出現異常之後的回撥,四個引數中還攜帶了異常堆疊資訊
@Nullable
ModelAndView resolveException(
HttpServletRequest request,HttpServletResponse response,@Nullable Object handler,Exception ex);
複製程式碼
我們自定義異常處理類就比較簡單了,實現上面的介面,然後將完整的堆疊返回給呼叫方
public class SelfExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,Object handler,Exception ex) {
String msg = GlobalExceptionHandler.getThrowableStackInfo(ex);
try {
response.addHeader("Content-Type","text/html; charset=UTF-8");
response.getWriter().append("自定義異常處理!!! \n").append(msg).flush();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
// 堆疊資訊列印方法如下
public static String getThrowableStackInfo(Throwable e) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
e.printStackTrace(new java.io.PrintWriter(buf,true));
String msg = buf.toString();
try {
buf.close();
} catch (Exception t) {
return e.getMessage();
}
return msg;
}
複製程式碼
仔細觀察上面的程式碼實現,有下面幾個點需要注意
- 為了確保中文不會亂碼,我們設定了返回頭
response.addHeader("Content-Type","text/html; charset=UTF-8");
如果沒有這一行,會出現中文亂碼的情況 - 我們純後端應用,不想返回檢視,直接想Response的輸出流中寫入資料返回
response.getWriter().append("自定義異常處理!!! \n").append(msg).flush();
; 如果專案中有自定義的錯誤頁面,可以通過返回ModelAndView
來確定最終返回的錯誤頁面 - 上面一個程式碼並不會直接生效,需要註冊,可以在
WebMvcConfigurer
的子類中實現註冊,例項如下
@SpringBootApplication
public class Application implements WebMvcConfigurer {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(0,new SelfExceptionHandler());
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
複製程式碼
2. 測試case
我們依然使用上篇博文的用例來測試
@Controller
@RequestMapping(path = "page")
public class ErrorPageRest {
@ResponseBody
@GetMapping(path = "divide")
public int divide(int sub) {
return 1000 / sub;
}
}
複製程式碼
下面分別是404異常和500異常的實測情況
500異常會進入我們的自定義異常處理類,而404依然走的是預設的錯誤頁面,所以如果我們需要捕獲404異常,依然需要在配置檔案中新增
# 出現錯誤時,直接丟擲異常
spring.mvc.throw-exception-if-no-handler-found=true
# 設定靜態資源對映訪問路徑
spring.mvc.static-path-pattern=/statics/**
# spring.resources.add-mappings=false
複製程式碼
為什麼404需要額外處理?
下面儘量以通俗易懂的方式說明下這個問題
- java web應用,除了返回json類資料之外還可能返回網頁,js,css
- 我們通過
@ResponseBody
來表明一個url返回的是json資料(通常情況下是這樣的,不考慮自定義實現) - 我們的
@Controller
中通過@RequestMapping
定義的REST服務,返回的是靜態資源 - 那麼js,css,圖片這些檔案呢,在我們的web應用中並不會定義一個REST服務
- 所以當接收一個http請求,找不到url關聯對映時,預設場景下不認為這是一個
NoHandlerFoundException
,不拋異常,而是到靜態資源中去找了(靜態資源中也沒有,為啥不拋NoHandlerFoundException呢?這個異常表示這個url請求沒有對應的處理器,但是我們這裡呢,給它分配到了靜態資源處理器了ResourceHttpRequestHandler
)
針對上面這點,如果有興趣深挖的同學,這裡給出關鍵程式碼位置
// 進入方法: `org.springframework.web.servlet.DispatcherServlet#doDispatch`
// debug 節點
Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest,response);
return;
}
// 核心邏輯
// org.springframework.web.servlet.DispatcherServlet#getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
複製程式碼
3. 小結
本篇博文雖然也介紹了一種新的全域性異常處理方式,實現效果和ControllerAdvice
也差不多,但是並不推薦用這種方法,原因如下
-
HandlerExceptionResolver
的方式沒有ControllerAdvice
方式簡介優雅 - 官方提供的
DefaultHandlerExceptionResolver
已經非常強大了,基本上覆蓋了http的各種狀態碼,我們自己再去定製的必要性不大
II. 其他
web系列博文
- 191010-SpringBoot系列教程web篇之全域性異常處理
- 190930-SpringBoot系列教程web篇之404、500異常頁面配置
- 190929-SpringBoot系列教程web篇之重定向
- 190913-SpringBoot系列教程web篇之返回文字、網頁、圖片的操作姿勢
- 190905-SpringBoot系列教程web篇之中文亂碼問題解決
- 190831-SpringBoot系列教程web篇之如何自定義引數解析器
- 190828-SpringBoot系列教程web篇之Post請求引數解析姿勢彙總
- 190824-SpringBoot系列教程web篇之Get請求引數解析姿勢彙總
- 190822-SpringBoot系列教程web篇之Beetl環境搭建
- 190820-SpringBoot系列教程web篇之Thymeleaf環境搭建
- 190816-SpringBoot系列教程web篇之Freemaker環境搭建
- 190421-SpringBoot高階篇WEB之websocket的使用說明
- 190327-Spring-RestTemplate之urlencode引數解析異常全程分析
- 190317-Spring MVC之基於java config無xml配置的web應用構建
- 190316-Spring MVC之基於xml配置的web應用構建
- 190213-SpringBoot檔案上傳異常之提示The temporary upload location xxx is not valid
專案原始碼
1. 一灰灰Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰Blog個人部落格 blog.hhui.top
- 一灰灰Blog-Spring專題部落格 spring.hhui.top