1. 程式人生 > 程式設計 >Springboot錯誤處理機制實現原理解析

Springboot錯誤處理機制實現原理解析

1.預設的錯誤機制

預設效果

①在瀏覽器中訪問不存在的請求時,springboot預設返回一個空白頁面

Springboot錯誤處理機制實現原理解析

瀏覽器的請求頭

Springboot錯誤處理機制實現原理解析

②客戶端訪問時,返回json資料

{
  "timestamp": "2020-03-24T02:49:56.572+0000","status": 404,"error": "Not Found","message": "No message available","path": "/"
}

客戶端訪問的請求頭

Springboot錯誤處理機制實現原理解析

原理

  可以參照 ErrorMvcAutoConfiguration 錯誤處理的自動配置

  給容器中添加了以下元件

    1.DefaultErrorAttributes

public Map<String,Object> getErrorAttributes(WebRequest webRequest,boolean includeStackTrace) {
  Map<String,Object> errorAttributes = new LinkedHashMap();
  errorAttributes.put("timestamp",new Date());
  this.addStatus(errorAttributes,webRequest);
  this.addErrorDetails(errorAttributes,webRequest,includeStackTrace);
  this.addPath(errorAttributes,webRequest);
  return errorAttributes;
}
@RequestMapping(
    produces = {"text/html"}
  )
  public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {
    HttpStatus status = this.getStatus(request);
    //處理頁面的請求返回給前臺資料 model 的獲取 ,呼叫
    Map<String,Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request,this.isIncludeStackTrace(request,MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = this.resolveErrorView(request,response,status,model);
    return modelAndView != null ? modelAndView : new ModelAndView("error",model);
  }
 
 
 
//呼叫 AbstractErrorController#getErrorAttributes
  protected Map<String,Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) {
    WebRequest webRequest = new ServletWebRequest(request);
    return this.errorAttributes.getErrorAttributes(webRequest,includeStackTrace);
  }
 
 
 
最終呼叫DefaultErrorAttributes#getErrorAttributes
  public Map<String,boolean includeStackTrace) {

    2.BasicErrorController : 處理預設的 /error 請求

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"}) 
public class BasicErrorController extends AbstractErrorController {
  private final ErrorProperties errorProperties;
public String getErrorPath() {
  return this.errorProperties.getPath();
}

@RequestMapping(
  produces = {"text/html"}  //產生html型別的資料,瀏覽器傳送的請求來到這個方法處理
)
public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {
  //獲取狀態碼
  HttpStatus status = this.getStatus(request);
  //獲取模型資料
  Map<String,MediaType.TEXT_HTML)));
  response.setStatus(status.value());
  //去哪個頁面作為錯誤頁面,包括頁面地址和內容
  ModelAndView modelAndView = this.resolveErrorView(request,model);
  return modelAndView != null ? modelAndView : new ModelAndView("error",model);
}

@RequestMapping //產生json型別的資料, 其他客戶端傳送的請求來到這個方法處理
public ResponseEntity<Map<String,Object>> error(HttpServletRequest request) {
  HttpStatus status = this.getStatus(request);
  if (status == HttpStatus.NO_CONTENT) {
    return new ResponseEntity(status);
  } else {
    Map<String,Object> body = this.getErrorAttributes(request,MediaType.ALL));
    return new ResponseEntity(body,status);
  }
}

3.ErrorPageCustomizer

public class ErrorProperties {
  @Value("${error.path:/error}")
  private String path = "/error";  //系統出現錯誤請求之後來到 /error 請求進行處理 ,(類似於以前 web.xml 中註冊的錯誤頁面規則)

    4.DefaultErrorViewResolver

public ModelAndView resolveErrorView(HttpServletRequest request,HttpStatus status,Map<String,Object> model) {
    ModelAndView modelAndView = this.resolve(String.valueOf(status.value()),model);
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
      modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()),model);
    }
 
    return modelAndView;
  }
 
  private ModelAndView resolve(String viewName,Object> model) {    //預設 springboot 可以找到這個頁面 error/404
    String errorViewName = "error/" + viewName;    //模板引擎可以解析這個頁面地址就用模板引擎解析
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);    //模板引擎可用的情況下就返回到 errorViewName 指定的檢視地址
    return provider != null ? new ModelAndView(errorViewName,model) : this.resolveResource(errorViewName,model);
  }
   //模板引擎不可用就在靜態資原始檔夾裡面找 errorViewName 對應的頁面  error/404.html
  private ModelAndView resolveResource(String viewName,Object> model) {
    String[] var3 = this.resourceProperties.getStaticLocations();
    int var4 = var3.length;
 
    for(int var5 = 0; var5 < var4; ++var5) {
      String location = var3[var5];
 
      try {
        Resource resource = this.applicationContext.getResource(location);
        resource = resource.createRelative(viewName + ".html");          //如果靜態資原始檔中由 這個資源就直接使用,否則返回為空
        if (resource.exists()) {
          return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource),model);
        }
      } catch (Exception var8) {
      }
    }
     
    return null;
  }

    步驟:

      一旦系統出現 4xx 或者 5xx 之類的錯誤,ErrorPageCustomizer 就會生效(定製錯誤的響應規則),就會來到 /error 請求,會被BasicErrorController

處理。

①響應頁面 去哪個頁面由 DefaultErrorViewResolver 決定

protected ModelAndView resolveErrorView(HttpServletRequest request,HttpServletResponse response,Object> model) {
  Iterator var5 = this.errorViewResolvers.iterator();     //解析所有的 ErrorViewResolver 得到 modelAndView
  ModelAndView modelAndView;
  do {
    if (!var5.hasNext()) {
      return null;
    }
    ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
    modelAndView = resolver.resolveErrorView(request,model);
  } while(modelAndView == null);
  return modelAndView;
}

2.錯誤資訊的定製

①如何定製錯誤頁面

  1>有模板引擎的情況下: error/狀態碼 ;【將錯誤頁面命名為 錯誤碼.html 放在模板引擎資料夾下的 error 資料夾下】,發生此狀態碼的錯誤就來到

  對應的頁面;

我們可以使用 4xx 和 5xx 作為錯誤頁面的檔名來匹配這種型別的所欲錯誤,精確優先(優先尋找精確的 狀態碼.html );

  頁面能夠獲取到的資訊

      timestamp :時間戳

      status : 狀態碼

      exception : 異常物件

      message : 異常訊息

      errors : JSR303資料校驗的錯誤都在這兒

    2>.沒有模板引擎(模板引擎找不到這個頁面),靜態資原始檔夾下找

    3>.以上都沒有錯誤頁面,就預設來到 springboot 預設的錯誤頁面

②、自定義異常處理&返回定製json資料;

@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;
  }
}
//通過異常處理器,但沒有自適應效果(瀏覽器返回頁面,客戶端訪問返回json資料)

2)、轉發到/error進行自適應響應效果處理

@RequestMapping(
  produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {     //獲取錯誤的狀態碼,在分析的過程中,要注意引數從哪兒來?  =======》前領導的一句話,哈哈……
  HttpStatus status = this.getStatus(request);
  Map<String,MediaType.TEXT_HTML)));
  response.setStatus(status.value());    //依據錯誤狀態碼解析錯誤試圖,如果直接轉發,不指定錯誤狀態碼則試圖解析出錯(直接轉發狀態碼為 200 ,到不了定製的 4xx 5xx 的頁面)
  ModelAndView modelAndView = this.resolveErrorView(request,model);
}
@ExceptionHandler(UserNotExistException.class)
  public String handleException(Exception e,HttpServletRequest request){
    Map<String,Object> map = new HashMap<>();
    <strong>//傳入我們自己的錯誤狀態碼 4xx 5xx,否則就不會進入定製錯誤頁面的解析流程</strong>
    /**
     * Integer statusCode = (Integer) request
     .getAttribute("javax.servlet.error.status_code");
     */
    request.setAttribute("javax.servlet.error.status_code",500);
    map.put("code",e.getMessage());
    //轉發到/error
    return "forward:/error";
  }

3)、將我們的定製資料攜帶出去;======》即修改model中的值即可

出現錯誤以後,會來到/error請求,會被BasicErrorController處理,響應出去可以獲取的資料是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)規定的方法);

1、完全來編寫一個ErrorController的實現類【或者是編寫AbstractErrorController的子類】,放在容器中;

2、頁面上能用的資料,或者是json返回能用的資料都是通過errorAttributes.getErrorAttributes得到;

容器中DefaultErrorAttributes.getErrorAttributes();預設進行資料處理的;

自定義ErrorAttributes

//給容器中加入我們自己定義的ErrorAttributes
@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","atguigu");
    return map;
  }
}

最終的效果:響應是自適應的,可以通過定製ErrorAttributes改變需要返回的內容,

Springboot錯誤處理機制實現原理解析

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。