SpringBoot--Web篇<一>
SpringBoot--Web篇<二>
7. 錯(cuò)誤處理機(jī)制
7.1 SpringBoot默認(rèn)的錯(cuò)誤處理機(jī)制
默認(rèn)效果:
1)瀏覽器,返回一個(gè)默認(rèn)的錯(cuò)誤頁(yè)面
此時(shí)可以查看瀏覽器發(fā)送請(qǐng)求的請(qǐng)求頭:
如果是其他客戶端,默認(rèn)響應(yīng)一個(gè)json數(shù)據(jù)
{
"timestamp": 1534750087344,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/aaa"
}
原理:
? 可以參照ErrorMvcAutoConfiguration
: 錯(cuò)誤處理的自動(dòng)配置類;
? 說(shuō)明:基于SpringBoot 1.5.x 源碼;
? 給容器中添加了以下組件:
? 1手形、DefaultErrorAttributes:幫我們?cè)陧?yè)面共享信息;
//ErrorMvcAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
// DefaultErrorAttributes.java
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
? 2、BasicErrorController: 處理默認(rèn)的 /error請(qǐng)求
//ErrorMvcAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
//BasicErrorController.java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
//產(chǎn)生html類型的數(shù)據(jù);瀏覽器發(fā)送的請(qǐng)求來(lái)到這個(gè)方法處理
@RequestMapping(produces = "text/html")
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());
//去哪個(gè)頁(yè)面作為錯(cuò)誤頁(yè)面翰守;包含頁(yè)面地址和頁(yè)面內(nèi)容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
////產(chǎn)生json數(shù)據(jù),其他客戶端來(lái)到這個(gè)方法處理疲酌;
@RequestMapping
@ResponseBody
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<Map<String, Object>>(body, status);
}
? 3蜡峰、ErrorPageCustomizer
@Value("${error.path:/error}")
private String path = "/error"; //系統(tǒng)出現(xiàn)錯(cuò)誤以后來(lái)到error請(qǐng)求進(jìn)行處理了袁;(web.xml注冊(cè)的錯(cuò)誤頁(yè)面規(guī)則)
? 4.、DefaultErrorViewResolver
//ErrorMvcAutoConfiguration.java
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
}
//DefaultErrorViewResolver.java
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), 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) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
? 步驟:
? 一但系統(tǒng)出現(xiàn)4xx或者5xx之類的錯(cuò)誤湿颅;ErrorPageCustomizer就會(huì)生效(定制錯(cuò)誤的響應(yīng)規(guī)則)载绿;就會(huì)來(lái)到/error請(qǐng)求;就會(huì)被BasicErrorController處理油航;
? 響應(yīng)頁(yè)面崭庸;去哪個(gè)頁(yè)面是由DefaultErrorViewResolver解析得到的;
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
7.2 如何定制錯(cuò)誤響應(yīng)
7.2.1 如何定制錯(cuò)誤的頁(yè)面
? 1) 有模板引擎的情況下谊囚;error/狀態(tài)碼; 【將錯(cuò)誤頁(yè)面命名為 錯(cuò)誤狀態(tài)碼.html 放在模板引擎文件夾里面的 error文件夾下】怕享,發(fā)生此狀態(tài)碼的錯(cuò)誤就會(huì)來(lái)到 對(duì)應(yīng)的頁(yè)面;
? 我們可以使用4xx和5xx作為錯(cuò)誤頁(yè)面的文件名來(lái)匹配這種類型的所有錯(cuò)誤镰踏,精確優(yōu)先(優(yōu)先尋找精確的狀態(tài)碼.html)函筋;
頁(yè)面能獲取的信息:
timestamp:時(shí)間戳
status:狀態(tài)碼
error:錯(cuò)誤提示
exception:異常對(duì)象
message:異常消息
errors:JSR303數(shù)據(jù)校驗(yàn)的錯(cuò)誤都在這里
? 2) 沒(méi)有模板引擎(模板引擎找不到這個(gè)錯(cuò)誤頁(yè)面),靜態(tài)資源文件夾下找奠伪;
? 3)以上都沒(méi)有錯(cuò)誤頁(yè)面跌帐,就是默認(rèn)來(lái)到SpringBoot默認(rèn)的錯(cuò)誤提示頁(yè)面;
7.2.2 如何定制錯(cuò)誤的json數(shù)據(jù)
? 1)自定義異常處理&返回定制json數(shù)據(jù)芳来;
//MyExceptionHandler.java
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
? 2)轉(zhuǎn)發(fā)到/error進(jìn)行自適應(yīng)響應(yīng)效果處理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
//傳入我們自己的錯(cuò)誤狀態(tài)碼 4xx 5xx含末,否則就不會(huì)進(jìn)入定制錯(cuò)誤頁(yè)面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
//轉(zhuǎn)發(fā)到/error
return "forward:/error";
}
? 3)將我們的定制數(shù)據(jù)攜帶出去;
? 出現(xiàn)錯(cuò)誤以后即舌,會(huì)來(lái)到/error請(qǐng)求佣盒,會(huì)被BasicErrorController處理,響應(yīng)出去可以獲取的數(shù)據(jù)是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)規(guī)定的方法)顽聂;
? 1肥惭、完全來(lái)編寫一個(gè)ErrorController的實(shí)現(xiàn)類【或者是編寫AbstractErrorController的子類】,放在容器中紊搪;
? 2蜜葱、頁(yè)面上能用的數(shù)據(jù),或者是json返回能用的數(shù)據(jù)都是通過(guò)errorAttributes.getErrorAttributes得到耀石;
? 容器中DefaultErrorAttributes.getErrorAttributes()牵囤;默認(rèn)進(jìn)行數(shù)據(jù)處理的;
自定義ErrorAttributes
package com.example.springboot_restcrud.component;
import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import java.util.Map;
/**
* 向容器中注入我們自定義的 ErrorAttributes
* @Author cyy
* @Date 2018/8/20 17:10
* @Version 1.0
* @Blog http://pccwcyy.club/wordpress/
**/
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","pccw");
//我們的異常處理器攜帶的數(shù)據(jù)
// Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext",0);
// map.put("ext",ext);
return map;
}
}
最終的效果:響應(yīng)是自適應(yīng)的滞伟,可以通過(guò)定制ErrorAttributes改變需要返回的內(nèi)容