07.Spring Boot 之錯誤處理機制

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 請求錯誤

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 文件夾下

也可以使用 4xx5xx 作爲錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的 狀態碼.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;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章