SpingBoot錯誤信息處理及原理

SpringBoot錯誤信息處理機制

在一個web項目中,總須要對一些錯誤進行界面或者json數據返回,已實現更好的用戶體驗,SpringBoot中提供了對於錯誤處理的自動配置html

ErrorMvcAutoConfiguration這個類存放了全部關於錯誤信息的自動配置。java

1. SpringBoot處理錯誤請求的流程

訪問步驟:web

  • 首先客戶端訪問了錯誤界面。例:404或者500
  • SpringBoot註冊錯誤請求/error。經過ErrorPageCustomizer組件實現
  • 經過BasicErrorController處理/error,對錯誤信息進行了自適應處理,瀏覽器會響應一個界面,其餘端會響應一個json數據
  • 若是響應一個界面,經過DefaultErrorViewResolver類來進行具體的解析。能夠經過模板引擎解析也能夠解析靜態資源文件,若是二者都不存在則直接返回默認的錯誤JSON或者錯誤View
  • 經過DefaultErrorAttributes來添加具體的錯誤信息

源代碼spring

//錯誤信息的自動配置
public class ErrorMvcAutoConfiguration {
    //響應具體的錯誤信息
    @Bean
    public DefaultErrorAttributes errorAttributes() {
        return
    }
    //處理錯誤請求
    @Bean
    public BasicErrorController basicErrorController() {
        return 
    }
    //註冊錯誤界面
    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
        return 
    }
//註冊錯誤界面,錯誤界面的路徑爲/error
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
    //服務器基本配置
    private final ServerProperties properties;
    
    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
        //獲取服務器配置中的錯誤路徑/error
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
        //註冊錯誤界面
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }   
    
    //this.properties.getError()
public class ServerProperties{
    //錯誤信息的配置文件
    private final ErrorProperties error = new ErrorProperties();
}
   //getPath
public class ErrorProperties {
    @Value("${error.path:/error}")
    private String path = "/error";
//處理/error請求,從配置文件中取出請求的路徑
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    
    //瀏覽器行爲,經過請求頭來判斷,瀏覽器返回一個視圖
    @RequestMapping(
        produces = {"text/html"}
 
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        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);
    }
    
    //其餘客戶端行爲處理,返回一個JSON數據
    @RequestMapping
    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, this.isIncludeStackTrace(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
//添加錯誤信息
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    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;
    }

2. 響應一個視圖

步驟:json

  • 客戶端出現錯誤
  • SpringBoot建立錯誤請求/error
  • BasicErrorController處理請求
@RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //解析錯誤界面,返回一個ModelAndView,調用父類AbstractErrorController的方法
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }
public abstract class AbstractErrorController{
     private final List<ErrorViewResolver> errorViewResolvers;
    
    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();
        //遍歷全部的錯誤視圖解析器
        ModelAndView modelAndView;
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            //調用視圖解析器的方法,
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }
}
public interface ErrorViewResolver {
    ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}
  • 處理具體的視圖跳轉
//處理視圖跳轉
public 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;
    }
//視圖名稱爲error文件夾下的400.html等狀態碼文件
  private ModelAndView resolve(String viewName, Map<String, Object> model) {
        String errorViewName = "error/" + viewName;
      //是否存在模板引擎進行解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
      //存在則返回解析之後的數據,不存在調用resolveResource方法進行解析
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }  
    
    //若是靜態資源文件中存在,返回靜態文件下的,若是不存在返回SpringBoot默認的
    private ModelAndView resolveResource(String viewName, Map<String, 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;
    }

應用:瀏覽器

  • 在模板引擎文件下建立error文件夾,裏面放置各類狀態碼的視圖文件,模板引擎會解析
  • 在靜態資源下常見error文件夾,裏面放置各類狀態碼的視圖文件,模板引擎不會解析
  • 若是沒有狀態碼文件,則返回springBoot默認界面視圖

3.響應一個json數據

BasicErrorController處理/error請求的時候不適用瀏覽器默認請求springboot

@RequestMapping
    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, this.isIncludeStackTrace(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
父類方法:
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        //調用ErrorAttributes接口的getErrorAttributes方法,
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }
添加錯誤信息
public class 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;
    }

返回的json數據有:服務器

  • status
  • error
  • exception
  • message
  • trace
  • path

能夠經過模板引擎獲取這些值app

4.自定義異常返回自定義的異常數據

4.1@ControllerAdvice註解

SpringMVC提供的註解,能夠用來定義全局異常,全局數據綁定,全局數據預處理ide

@ControllerAdivice定義全局的異常處理

  • 經過@ExceptionHandler(XXXException.class)執行該方法須要處理什麼異常,而後返回什麼數據或者視圖
//json數據返回  ,處理自定義用戶不存在異常
    @ResponseBody
    @ExceptionHandler(UserException.class)
    public Map<String,String> userExceptionMethod(UserException us){
        Map<String,String> map = new HashMap<>();
        map.put("message",us.getMessage());
        return map ;
    }

@ControllerAdvice定義全局數據

  • 經過@ModelAttribute(Name="key")定義全局數據的key
  • 默認方法的返回值的名稱做爲鍵
  • Controller中經過Model獲取對應的key的值
@ControllerAdvice
public MyConfig{
    @ModelAttribute(name = "key")
    public Map<String,String> defineAttr(){
        Map<String,String> map = new HashMap<>();
        map.put("message","幻聽");
        map.put("update","許嵩");
        return map ;
    }
    
@Controller
public UserController{
    @GetMapping("/hello")
    public Map<String, Object> hello(Model model){
        Map<String, Object> asMap = model.asMap();
        System.out.println(asMap);
        //{key={message='上山',update='左手一式太極拳'}}
        return asMap ;
    }
}

@ControllerAdvice處理預處理數據(當須要添加的實體,屬性名字相同的時候)

  • Controller的參數中添加ModelAttribute做爲屬性賦值的前綴
  • ControllerAdvice修飾的類中,結合InitBinder來綁定對應的屬性(該屬性爲ModelAttribite的value值
  • @InitBinder修飾的方法中經過WebDataBinder添加默認的前綴
@Getter@Setter
public class Book {
    private String name ;
    private int age ;
    
@Getter@Setter
public class Music {
    private String name ;
    private String author ;
    
    //這種方式的處理,spring沒法判斷Name屬性給哪一個bean賦值,因此須要經過別名的方式來進行賦值
@PostMapping("book")
    public String book(Book book , Music music){
        System.out.println(book);
        System.out.println(music);
        return "404" ;
    }
    //使用如下的方式
@PostMapping("/book")
    public String book(@ModelAttribute("b")Book book , @ModelAttribute("m")Music music){
        System.out.println(book);
        System.out.println(music);
        return "404" ;
    }
    
public MyCOnfiguration{
    @InitBinder("b")
    public void b(WebDataBinder webDataBinder){
        webDataBinder.setFieldDefaultPrefix("b.");
    }
    @InitBinder("m")
    public void m(WebDataBinder webDataBinder){
        webDataBinder.setFieldDefaultPrefix("m.");
    }
}

4.2自定義異常JSON

瀏覽器和其餘客戶端都只能獲取json數據

@ControllerAdvice
public class MyExceptionHandler {

    //處理UserException異常
    @ResponseBody
    @ExceptionHandler(UserException.class)
    public Map<String,String> userExceptionMethod(UserException us){
        Map<String,String> map = new HashMap<>();
        map.put("message",us.getMessage());
        map.put("status","500");
        return map ;
    }

4.2自定義異常返回一個視圖,擁有自適應效果

@ExceptionHandler(UserException.class)
    public String allException(UserException e,HttpServletRequest request){
        Map<String,String> map = new HashMap<>();
        map.put("message",e.getMessage());
        map.put("load","下山");
        request.setAttribute("myMessage",map);
        //設置狀態碼,SpringBoot經過java.servlet.error.status_code來設置狀態碼
        request.setAttribute("javax.servlet.error.status_code",400);
        return "forward:/error" ;
    }

當拋出UserException異常的時候,來到這個異常處理器,給這個請求中添加了數據,再轉發到這個error請求中,交給ErrorPageCustomizer處理,因爲設置了請求狀態碼400則返回的視圖爲400或4XX視圖,或者直接返回一個JSON數據

{
    "timestamp": "2020-02-19T04:17:43.394+0000",
    "status": 400,
    "error": "Bad Request",
    "message": "用戶名不存在異常",
    "path": "/crud/user/login"
}

  • 不足:JSON數據中沒有顯示咱們本身定義的錯誤信息

4.3自定義錯誤信息

前面提到SpringBoot對錯誤信息的定義存在於DefaultErrorAttributes類的getErrorAttributes中,咱們能夠直接繼承這個類,或者實現ErrorAttributes接口,而後將咱們本身實現的錯誤處理器添加到容器中便可。

繼承DefaultErrorAttributes和實現ErrorAttributes接口的區別是,繼承之後仍然可使用SpringBoot默認的錯誤信息,咱們僅僅對該錯誤信息進行了加強;實現了ErrorAttributes接口,徹底自定義錯誤信息

  • 實現ErrorAttributes接口
public class MyErrorHandler implements ErrorAttributes {
    
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("status",500);
        errorAttributes.put("message","用戶不存在異常");
        return errorAttributes;
    }

    @Override
    public Throwable getError(WebRequest webRequest) {
        return null;
    }
  • 繼承DefaultErrorAttributes的方法
public class MyErrorHandler extends DefaultErrorAttributes {
    
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //調用父類方法,直接在默認錯誤信息的基礎上添加
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("message","用戶不存在異常");
        return errorAttributes;
    }

}
  • 將定義的錯誤信息器添加到容器中
    • 經過@Component組件直接將MyErrorHandler組件添加到容器中
    • 經過@Bean在配置類中將組件添加到容器中
@Bean
    public DefaultErrorAttributes getErrorHandler(){
        return new MyErrorHandler();
    }
  • 下面解決上一節中沒有出現咱們自定義的異常信息
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("message","用戶不存在異常");
        //指定從哪一個做用域中取值
        webRequest.getAttribute("myMessage", RequestAttributes.SCOPE_REQUEST);
        return errorAttributes;

將在異常處理器中定義的錯誤信息取出,而後添加到錯誤信息中。

相關文章
相關標籤/搜索