1. SpringBoot 默認的錯誤處理機制
代碼已經上傳至 https://github.com/masteryourself-tutorial/tutorial-spring ,詳見
tutorial-spring-boot-core/tutorial-spring-boot-errorhandler
工程
1.1 瀏覽器請求錯誤
瀏覽器發起的請求中,請求頭是 text/html
1.2 postman 請求錯誤
postman 發起的請求中,請求頭是 */*
1.3 錯誤處理原理
由於開啓了自動裝配,所以容器中導入了 ErrorMvcAutoConfiguration
組件,這個組件又向容器中添加了如下幾個組件
1.3.1 DefaultErrorAttributes
處理參數信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class,
search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException());
}
1.3.2 BasicErrorController
處理默認的 /error
請求,本質上就是一個 controller
@Bean
@ConditionalOnMissingBean(value = ErrorController.class,
search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
處理 ${server.error.path:${error.path:/error}
路徑的請求,默認是 /error
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
...
// 處理 text/html 請求
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 查找錯誤頁面,包含頁面地址和頁面內容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
// 產生 json 數據,其他客戶端來到這個方法處理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
...
}
1.3.3 ErrorPageCustomizer
系統出現錯誤以後來到 /error
請求進行處理
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
1.3.4 DefaultErrorViewResolverConfiguration
主要是向容器中注入 DefaultErrorViewResolver
組件
@Configuration
static class DefaultErrorViewResolverConfiguration {
...
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
}
}
DefaultErrorViewResolverConfiguration
這個類有如下兩個方法,用來返回 ModelAndView 對象,供 BasicErrorController
調用
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 默認 Spring Boot 可以去找到一個頁面 error/404
String errorViewName = "error/" + viewName;
// 模板引擎可以解析這個頁面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
// 模板引擎可用的情況下返回到 errorViewName 指定的視圖地址
return new ModelAndView(errorViewName, model);
}
// 模板引擎不可用,就在靜態資源文件夾下找 errorViewName 對應的頁面 error/404.html
return resolveResource(errorViewName, model);
}
2. 定製錯誤頁面
2.1 有模板引擎的情況
Spring Boot 錯誤頁面命名爲 錯誤狀態碼.html
,放在模板引擎文件夾裏面的 error
文件夾下
也可以使用 4xx
和 5xx
作爲錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的 狀態碼.html
)
頁面能夠獲取的信息有(見 org.springframework.boot.web.servlet.error.DefaultErrorAttributes
):
-
timestamp:時間戳
-
status:狀態碼
-
error:錯誤提示
-
exception:異常對象
-
message:異常消息
-
errors:JSR303 數據校驗的錯誤都在這裏
2.2 沒有模板引擎的情況
將會在靜態資源文件夾下找,查找原理同上
2.3 以上都沒有
使用 Spring Boot 提供默認的錯誤頁面,見 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView#render
3. 定製錯誤數據
3.1 @ControllerAdvice
沒有自適應效果,瀏覽器請求也會響應 json 數據,不夠優雅
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Map<String, Object> handleException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", "999999");
map.put("message", e.getMessage());
return map;
}
}
3.2 轉發到 /error
轉發到 /error
進行自適應響應效果處理,因爲 Spring Boot 提供的 /error
請求是自適應的
@ExceptionHandler(Exception.class)
public String handleException(Exception e, HttpServletRequest request) {
request.setAttribute("javax.servlet.error.status_code", 500);
request.setAttribute("author", "masteryourself");
// 轉發到 /error
return "forward:/error";
}
3.3 定製錯誤數據
出現錯誤以後,會來到 /error
請求,會被 BasicErrorController
處理,響應出去可以獲取的數據是由 getErrorAttributes()
得到的(是 AbstractErrorController
規定的方法),所以我們有兩種方法來解決這個問題
-
完全來編寫一個 ErrorController 的實現類(或者是編寫
AbstractErrorController
的子類),放在容器中 -
頁面上能用的數據,或者是 json 返回能用的數據都是通過
errorAttributes.getErrorAttributes()
得到,那麼就向容器中注入一個DefaultErrorAttributes
組件
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("author", webRequest.getAttribute("author", RequestAttributes.SCOPE_REQUEST));
return map;
}
}