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

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