🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
当我们使用浏览器访问一个不存在的地址时,SpringBoot 默认提供如下类似的404页面。 ![](https://img.kancloud.cn/ff/74/ff74752c0415f56c3b527446d683551d_887x218.png) 当如 Android、IOS 等非浏览器客户端访问时,比如使用 [Postman](https://www.postman.com/downloads/) 客户端软件模拟请求一个不存在的页面时响应的是json数据。 ![](https://img.kancloud.cn/6a/8f/6a8f461c15a18ef77e6a6ff249e3be58_1195x625.png) SpringBoot 的错误处理机制由 ErrorMvcAutoConfiguration 类管理,它的源码如下,它注册了下面4个错误处理组件。 ```java -----ErrorMvcAutoConfiguration----- @Bean @ConditionalOnMissingBean( value = {ErrorAttributes.class}, search = SearchStrategy.CURRENT ) // 组件1:DefaultErrorAttributes public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); } @Bean @ConditionalOnMissingBean( value = {ErrorController.class}, search = SearchStrategy.CURRENT ) // 组件2:BasicErrorController public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList())); } @Bean // 组件3:ErrorPageCustomizer public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) { return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, dispatcherServletPath); } @Bean @ConditionalOnBean({DispatcherServlet.class}) @ConditionalOnMissingBean({ErrorViewResolver.class}) 组件4:DefaultErrorViewResolver DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } ``` 这4个组件的调用步骤如下: 1. 当系统出现4xx或者5xx之类的错误 ,调用ErrorPageCustomizer来定制错误的响应规则,它会获取系统默认的/error页面; ```java -----ErrorMvcAutoConfiguration----- public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().▲▲▲▲getPath()▲▲▲▲)); errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage}); } -> -----ErrorProperties----- public class ErrorProperties { @Value("${error.path:/error}") private String path = "/error"; private boolean includeException; ``` 2. 接着调用BasicErrorController来处理这个/error页面,BasicErrorController 会根据请求的头部信息来判断是来自浏览器的请求,还是客户端的请求,决定应该响应一个页面,还是json数据。 ![](https://img.kancloud.cn/e2/b6/e2b695e4261009b0193677a18b9f02b4_633x139.png) ![](https://img.kancloud.cn/08/a4/08a4e88b591b879b069d609da7aca105_734x343.png) ```java -----ErrorMvcAutoConfiguration----- @Bean @ConditionalOnMissingBean( value = {ErrorController.class}, search = SearchStrategy.CURRENT ) public ▲▲▲▲BasicErrorController▲▲▲▲ basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList())); } -> -----BasicErrorController----- @Controller // 如果没有找到server.error.path,则采用errorp.path,如果还是没有则最后采用/error错误配置 @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { private final ErrorProperties errorProperties; @RequestMapping( produces = {"text/html"} ) // 当是浏览器访问时,返回html public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = // 获取模型数据 Collections.unmodifiableMap(▲▲▲▲this.getErrorAttributes(request, this.getErrorAttributeOptions(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.getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity(body, status); } } ``` 3. 接着DefaultErrorViewResolver起作用; 当是浏览器访问时,响应的是html,DefaultErrorViewResolver解析应该响应哪个页面; ```java -----ErrorMvcAutoConfiguration----- @Bean @ConditionalOnBean( { DispatcherServlet.class } ) @ConditionalOnMissingBean( { ErrorViewResolver.class } ) ▲▲▲▲DefaultErrorViewResolver▲▲▲▲ conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } ->-----DefaultErrorViewResolver----- // 解析出是客户端错误,还是服务端错误,客户端错误为4xx,服务端错误为5xx,将它们存入Map中 static { Map<Series, String>views=new EnumMap(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS=Collections.unmodifiableMap(views); } // 返回对应的视图 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())) { // 根据不同的状态码SERIES_VIEWS.get(status.series())解析出对应的视图 modelAndView=this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { // 将对应的视图拼接为error/viewName,比如如果是404状态码,则为error/404.html String errorViewName="error/"+viewName; TemplateAvailabilityProvider provider=this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); // 如果模板引擎可用,而且解析的错误页面可用,则用;如果不能用则用this.resolveResource(errorViewName, model)提供的错误页面 return provider !=null ? new ModelAndView(errorViewName, model): this.resolveResource(errorViewName, model); } // 到静态资源目录下寻找可用的error页面 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"); // 当静态资源目录下存在templates/error/404.html则用,不存在也是返回null if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception var8) {} } return null; } ``` 4. 最终由DefaultErrorAttributes来配置页面的共享信息 ```java -----DefaultErrorAttributes----- errorAttributes.put("status", 999); // 状态码 errorAttributes.put("error", "None"); // 错误提示 errorAttributes.put("exception", error.getClass().getName()); // 异常对象 errorAttributes.put("message", message); // 消息 errorAttributes.put("errors", result.getAllErrors()); // jsr303数据校验错误提示 errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("path", path); errorAttributes.put("timestamp", new Date()); ```