springboot錯誤處理原理

配置錯誤處理路徑

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
	//錯誤配置參數
    @NestedConfigurationProperty
    private ErrorProperties error = new ErrorProperties();
}

springboot的參數配置類當中有一個錯誤參數配置類.

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

這裏配置了錯誤處理路徑,默認的錯誤處理路徑是/error,我們可以通過在配置文件當中配置server.error.path進行修改

錯誤自動配置類ErrorMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ResourceProperties.class})
public class ErrorMvcAutoConfiguration {
    private final ServerProperties serverProperties;
    private final List<ErrorViewResolver> errorViewResolvers;

    public ErrorMvcAutoConfiguration(ServerProperties serverProperties, ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
        this.serverProperties = serverProperties;
        this.errorViewResolvers = (List)errorViewResolversProvider.getIfAvailable();
    }

這裏將springboot參數配置類ServerProperties 注入,同時也注入了ErrorProperties 。
ErrorMvcAutoConfiguration 中注入了ErrorPageCustomizer對錯誤鏈接進行註冊。

 @Bean
   public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
       return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
   }
   private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
       private final ServerProperties properties;

       protected ErrorPageCustomizer(ServerProperties properties) {
           this.properties = properties;
       }

       public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
       	//註冊/error或者配置文件中的server.error.path值
           ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath());
           errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
       }

       public int getOrder() {
           return 0;
       }
   }

ErrorMvcAutoConfiguration 向容器中注入了BasicErrorController對/error或者配置文件中配置的錯誤路徑進行處理

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

   public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
       this(errorAttributes, errorProperties, Collections.emptyList());
   }

   public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
       super(errorAttributes, errorViewResolvers);
       Assert.notNull(errorProperties, "ErrorProperties must not be null");
       this.errorProperties = errorProperties;
   }

   public String getErrorPath() {
       return this.errorProperties.getPath();
   }

   @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 ? new ModelAndView("error", model) : modelAndView;
   }
   //對其他客戶端的錯誤請求進行處理
   @RequestMapping
   @ResponseBody
   public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
       Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
       HttpStatus status = this.getStatus(request);
       return new ResponseEntity(body, status);
   }

errorHtml是對網頁端錯誤請求的處理,error是對其他客戶端錯誤請求的處理

對網頁端錯誤請求的處理

	@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);
        //如果沒有獲取視圖解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView
        return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
    }

對錯誤信息的處理

this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)))
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
    }
 

這裏的RequestAttributes 就是在ErrorMvcAutoConfiguration 中注入容器的DefaultErrorAttributes

	//ErrorMvcAutoConfiguration 代碼
    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }

DefaultErrorAttributes中的getErrorAttributes方法

public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());//放入timestamp
        this.addStatus(errorAttributes, requestAttributes);//放入status
        this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);//放入exception和message
        this.addPath(errorAttributes, requestAttributes);//放入path
        return errorAttributes;
    }

將上面的分析,我們能在頁面展示的信息有時間戳(timestamp),錯誤狀態碼(status),異常對象(exception),異常消息(message),錯誤路徑(path)

獲取視圖解析器

ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
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;
    }

ErrorViewResolver 就只在ErrorMvcAutoConfiguration中注入容器的DefaultErrorViewResolver

	@Bean
    @ConditionalOnBean({DispatcherServlet.class})
     @ConditionalOnMissingBean
     public DefaultErrorViewResolver conventionErrorViewResolver() {
         return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
     }
 modelAndView = resolver.resolveErrorView(request, status, model);
 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status), 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, Map<String, Object> model) {
        String errorViewName = "error/" + viewName;//   error/錯誤狀態碼  error/400
        //如果有模板引擎,就在模板引擎中去找   error/錯誤狀態碼   頁面
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //如果模板引擎當中沒有找到相應的頁面,再去靜態資源下找,如果還沒有返回null
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }
    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;
    }

如果沒有獲取視圖解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView。

private final ErrorMvcAutoConfiguration.SpelView defaultErrorView = new ErrorMvcAutoConfiguration.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>");

       protected WhitelabelErrorViewConfiguration() {
       }

       @Bean(
           name = {"error"}
       )
       @ConditionalOnMissingBean(
           name = {"error"}
       )
       public View defaultErrorView() {
           return this.defaultErrorView;
       }

我們如何定義自己的錯誤頁面

有模板引擎的話,

在模板中配置error/4xx或者error/5xx頁面(頁面後綴根據引擎來寫),

沒有模板引擎

在靜態資源下配置error/4xx或者error/5xx頁面

以上都沒有

以上都沒有錯誤頁面,就是默認來到SpringBoot默認的錯誤提示頁面;

對上述原理的實際用途,自定義錯誤json數據

自定義異常處理增強


@ControllerAdvice
public class HandlerException {
	//@ExceptionHandler標註對那些錯誤進行增強,如果同一處理可以寫成Exception.class,如果想對錯誤單獨處理,配置自己的錯誤
    @ExceptionHandler(Exception.class)
    public String handler(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        map.put("code","1001");
        map.put("msg","錯誤了");
        request.setAttribute("javax.servlet.error.status_code",500);
        request.setAttribute("ext",map);
        e.printStackTrace();
        return "forward:/error";
    }
    //如果Exception和BindException都進行不同的處理,那麼如果發生BindException,優先有此方法進行處理,其他異常由handler方法處理
    @ExceptionHandler(BindException.class)
    public String bindExceptionHandler(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        map.put("code","1002");
        map.put("msg","錯誤了1002");
        //設置狀態碼。是爲了讓springboot自動識別賺到相應頁面
        request.setAttribute("javax.servlet.error.status_code",500);
        request.setAttribute("ext",map);
        e.printStackTrace();
        //使用請求轉發是爲了利用springboot爲我們自動識別是網頁還是其他客戶端
        return "forward:/error";
    }
}

將自定義的異常信息加入加入springboot返回頁面

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
    	//獲取springboot定義的異常數據
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        //獲取我們自己定義的異常數據
        Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
        //將我們的自己定義的異常數據放到map中返回
        map.put("ext",ext);
        return map;
    }
}

這是利用自動配置當中對DefaultErrorAttributes注入條件

	@Bean
	//只有當容器中沒有ErrorAttributes類型的bean是,纔會將DefaultErrorAttributes注入容器,上面我們定義了自己的MyErrorAttributes繼承於DefaultErrorAttributes,病注入容器當中,所以此時DefaultErrorAttributes將不在注入到容器中。進行錯誤信息處理時會用我們自己編寫的MyErrorAttributes
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }

只有當容器中沒有ErrorAttributes類型的bean是,纔會將DefaultErrorAttributes注入容器,上面我們定義了自己的MyErrorAttributes繼承於DefaultErrorAttributes,並注入容器當中,所以此時DefaultErrorAttributes將不在注入到容器中。進行錯誤信息處理時會用我們自己編寫的MyErrorAttributes

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