目录
- 1. SpringBoot自己对错误进行处理
- 1.1 给一个Controller进行错误处理
- 1.2 使用ControllerAdvice统一处理错误
- 2. 默认机制源码解析
- 3. 错误处理机制实战
1. SpringBoot自己对错误进行处理
1.1 给一个Controller进行错误处理
使用@ExceptionHandler,处理一个@Contoller的异常
package com.hh.springboot3test.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class ControllerTest {
/*
对整个RestController的异常进行处理
*/
@ResponseBody // 将异常消息返回给客户端
@ExceptionHandler(Exception.class) // 标识能处理的异常
public String handleException(Exception e) { // 接收异常
return "出错了, 原因: " + e.getMessage();
}
}
1.2 使用ControllerAdvice统一处理错误
使用@ControllerAdvice处理所有@Controller发生的错误。优先级比@Controller类的低
package com.hh.springboot3test.handler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
// 标明这个类是集中处理所有@Controller发生的错误
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "出错了, 统一处理,原因: " + e.getMessage();
}
}
2. 默认机制源码解析
如果我们没有进行自定义的错误处理,springBoot提供默认的错误处理机制。错误处理的自动配置都在ErrorMvcAutoConfiguration中。整个处理流程如下:
如果SpringMVC处理不了,则将错误请求发送给server.error.path=/error
参数配置的路径,如果没有配置参数,则默认是/error。由ErrorMvcAutoConfiguration添加的BasicErrorController组件进行处理。如下所示:
......省略部分......
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
......省略部分......
}
SpringBoot会自适应处理错误,浏览器优先响应页面或移动端优先响应JSON数据。和内容协商一样,header头的accept参数设置application/xml,则以XML格式返回数据。BasicErrorController部分代码如下所示:
@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);
}
@RequestMapping // 返回ResponseEntity, 可以是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, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
我们先说HTML的错误处理。在errorHtml方法。错误页面先调用resolveErrorView,通过DefaultErrorViewResolver解析的
// 解析错误的自定义视图地址
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
ErrorMvcAutoConfiguration给容器专门添加了一个错误视图解析器DefaultErrorViewResolver
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean({ErrorViewResolver.class})
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
SpringBoot的DefaultErrorViewResolver,提供了解析自定义错误页的默认规则
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// 先进行精确码处理
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
// 精确码处理不了,再进行4xx、5xx处理
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) {
// 先在classpath:templates/error路径下查找
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
// 如果classpath:templates/error没找到,则调用resolveResource方法,从静态路径下面的error文件夹下去找
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
String[] var3 = this.resources.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 HtmlResourceView(resource), model);
}
} catch (Exception var8) {
}
}
return null;
}
如果errorHtml方法,调用resolveErrorView处理不了。则在errorHtml方法访问classpath:templates/error.html
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
如果classpath:templates/error.html都未提供。ErrorMvcAutoConfiguration给容器添加一个默认名为error的view。提供了默认白板功能
@Bean(
name = {"error"}
)
@ConditionalOnMissingBean(
name = {"error"}
)
public View defaultErrorView() {
return this.defaultErrorView;
}
再说移动端的错误处理。DefaultErrorAttributes封装了JSON格式的错误信息
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
规则总结:
- 解析一个错误页
- 如果发生了403、404、500、503这些错误
- 如果有模板引擎,默认在classpath:/templates/error/精确码.html。如403.html文件
- 如果没有模板引擎,在静态资源文件夹下的error文件夹下找精确码.html
- 如果匹配不到精确码.html这些精确的错误页,就去找5xx.html,4xx.html模糊匹配
- 如果有模板引擎,默认在classpath:/templates/error/5xx.html。如5xx.html文件
- 如果没有模板引擎,在静态资源文件夹下的error文件夹下找 5xx.html
- 如果发生了403、404、500、503这些错误
- 如果上面处理不了,则从模板引擎路径classpath:templates下找error.html页面。如果error.html页面都没有,则使用springboot提供的默认白板页面
3. 错误处理机制实战
- 前后分离
- 后台发生的所有错误,@ControllerAdvice + @ExceptionHandler进行统一异常处理,将错误数据返回给前端
- 服务端页面渲染
- 不可预知的一些,HTTP码表示的服务器或客户端错误
- 给classpath:/templates/error/下面,放常用精确的错误码页面。如500.html、404.html
- 给classpath:/templates/error/下面,放通用模糊匹配的错误码页面。如5xx.html、4xx.html
- 发生业务错误
- 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页
- 通用业务,用classpath:/templates/error.html页面,显示错误信息
- 不可预知的一些,HTTP码表示的服务器或客户端错误
model中会默认存放一些数据。如下所示。我们自己也可以向model添加数据,然后再错误页面渲染的时候进行使用