SpringBoot 的錯誤處理機制

1,大體流程

一但系統出現4xx或者5xx之類的錯誤;ErrorPageCustomizer就會生效(定製錯誤的響應規則);就會來到/error請求;就會被BasicErrorController處理;
響應頁面;去哪一個頁面是由DefaultErrorViewResolver解析獲得的;html

2,錯誤自動配置類

ErrorMvcAutoConfiguration ,此類幫咱們自動配置了:java

@Bean
	public ErrorPageCustomizer errorPageCustomizer() {
		return new ErrorPageCustomizer(this.serverProperties);
	}

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
       /*添加錯誤映射,即發生錯誤時會發送 /error 請求*/
	   @Override
		public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {            
			ErrorPage errorPage = new ErrorPage(
					this.properties.getServlet().getServletPrefix()
							+ this.properties.getError().getPath());         
			errorPageRegistry.addErrorPages(errorPage);
		}
}

public class ErrorProperties {
	@Value("${error.path:/error}")
	private String path = "/error";

3,BasicErrorController 處理 /error 請求

/*返回頁面響應*/
@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());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        /*若是沒有找到對應的 /error/5xx.html 視圖 就會返回一個名爲 error 的ModelAndView*/
		return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
	}
/*返回json響應*/
	@RequestMapping
	@ResponseBody
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
        /*將響應狀態碼和響應數據封裝成ResponseEntity 返回*/
		return new ResponseEntity<>(body, status);
	}

①,getStatus

protected HttpStatus getStatus(HttpServletRequest request) {
        /*從request 獲取key爲 javax.servlet.error.status_code 的值*/
		Integer statusCode = (Integer) request
				.getAttribute("javax.servlet.error.status_code");
		if (statusCode == null) {
            /*獲取不到就響應一個500*/
			return HttpStatus.INTERNAL_SERVER_ERROR;
		}
		try {
			return HttpStatus.valueOf(statusCode);
		}
		catch (Exception ex) {
            /*發生錯誤也響應一個500*/
			return HttpStatus.INTERNAL_SERVER_ERROR;
		}
	}

②,getErrorAttributes 獲取錯誤響應數據

public abstract class AbstractErrorController implements ErrorController {
	private final ErrorAttributes errorAttributes;
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
			boolean includeStackTrace) {
		WebRequest webRequest = new ServletWebRequest(request);
        /*就是調用ErrorAttributes  裏的 getErrorAttributes 方法獲取響應數據*/
		return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
   }
}

public class ErrorMvcAutoConfiguration {
/*爲咱們註冊了一個DefaultErrorAttributes */
@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes(
				this.serverProperties.getError().isIncludeException());
	}
}
//即調用了DefaultErrorAttributes 的 getErrorAttributes 方法,以下:
	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}
/*
總的來講就是放了以下數據
頁面能獲取的信息;
timestamp:時間戳
status:狀態碼
error:錯誤提示
exception:異常對象
message:異常消息
errors:JSR303數據校驗的錯誤都在這裏
*/

③,name爲error 的ModelAndView

public class ErrorMvcAutoConfiguration {
    @Configuration
	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
	@Conditional(ErrorTemplateMissingCondition.class)
	protected static class WhitelabelErrorViewConfiguration {
        //這個即咱們常常看到的錯誤頁
		private final SpelView defaultErrorView = new SpelView(
				"<html><body><h1>Whitelabel Error Page</h1>"
						+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
						+ "<div id='created'>${timestamp}</div>"
						+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
						+ "<div>${message}</div></body></html>");
       // 便是這個View 
		@Bean(name = "error")
		@ConditionalOnMissingBean(name = "error")
		public View defaultErrorView() {
			return this.defaultErrorView;
		}

}

4,定製錯誤響應

①,編寫一個錯誤處理類,這裏仍是用到了@ControllerAdvice 註解web

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
//聲明是一個錯誤處理器
@ControllerAdvice
public class ErrorControllAdvice {

//    處理那種類型的錯誤
    @ExceptionHandler(Exception.class)
    public String errorHandle(HttpServletRequest request){
//        添加額外錯誤響應數據
        Map<String,Object> map=new HashMap<>();
//        添加一個公司的響應數據
        map.put("company","小米");
//        放到request 中,而後在咱們定製的DefaultErrorAttributes 裏獲取
        request.setAttribute("ext",map);
//        設置響應狀態碼
        request.setAttribute("javax.servlet.error.status_code",500);
//        轉發到/error 請求,這樣就能根據客戶端優化接收何種類型數據,決定響應html仍是json
        return "forward:/error";
    }
}

②,定製DefaultErrorAttributesspring

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@Component
public class MyDefaultErrorAttributes extends DefaultErrorAttributes {
    /*重寫getErrorAttributes 方法*/
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//        獲取原來的響應數據
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //從請求域中拿值
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
        //添加咱們定製的響應數據
        map.put("ext",ext);
//        返回帶有咱們定製的數據的map
        return map;
    }
}

5,測試

相關文章
相關標籤/搜索