1. 概述
本文主要讲解使用Spring boot框架进行web开发时如何进行异常处理。先分析默认的处理方式,然后再叙述如何自定义异常处理。在具体的分析之前,先看一下异常处理的整体图:
点击看大图
2. 默认异常处理
Spring boot提供了默认的处理异常方式,当出现异常时就会默认映射到 “/error”。处理异常的程序在类BasicErrorController中,该类提供了两种异常处理的方法,方法errorHtml用于处理浏览器端请求时出现的异常,方法error用于处理机器客户端请求时出现的异常。这两种请求的的区别在于请求头中Accept的值。值为 text/html时,方法errorHtml执行,返回HTML页面。值为application/json时,方法error执行,返回json数据。
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);
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);
}
2.1 浏览器客户端请求
2.1.1 白色错误标签页
如果我们的web的后端是用Spring boot开发,那么当用浏览器访问某个不存在的url时,就会返回一个白色错误标签页。上面显示一些错误信息和状态码等信息。如下图:
2.1.2 个性化错误页
上图对于客户来说不太友好,很多时候我们希望定义一些个性化的错误页面。例如优快云的404错误页面:
那么怎么实现该功能呢?你只需在 /error 文件夹下添加一个表示错误页面的文件。该文件可以是一个静态的HTML,也可以使用模板。这个文件的名字应该是精确的状态码或者是表示一个系列的模糊名称。如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftl
+- <other templates>
其背后的原理在于上面提到的errorHtml方法。当出现异常时,该方法会查询是否有在 error文件夹下提供对应错误状态码的静态资源文件,如果有则返回该文件,没有则返回上小节讲到的白色错误标签页。如果想要知道更详细的细节请查看相关源码。
2.2 机器客户端请求
当请求头中的Accept的值为application/json时,就会返回json数据了。出现异常时,BasicErrorController类中的error方法将被执行。会获取错误信息然后以Json格式返回。如下图:
3. 自定义异常处理
尽管Spring boot的默认异常处理已经很好了,但有时候我们想自定义异常处理。下面有两种方式自定义异常处理,对于应用级别的业务异常处理,我们可以通过注解@ControllerAdvice或@RestControllerAdvice来实现异常处理。但是上面的注解只能捕获处理应用级别的异常,例如Controller中抛出自定义的异常。却不能处理容器级别的异常,例如Filter中抛出的异常,请求参数错误异常等。要想处理容器级别的异常,需要继承BasicErrorController类,重写errorHtml和error方法。或者实现ErrorController接口,起到和类BasicErrorController相似的作用。
3.1 应用级别异常处理
下面主要讲解返回Json格式数据的情况,对于返回错误页面原理一样,在文章末尾有本文相关的代码都有实现。
下面是使用@ControllerAdvice定义的异常处理类GlobalExceptionHandler:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final String DEFAULT_ERROR_VIEW = "error";
@ExceptionHandler(value = GlobalErrorException.class)
@ResponseBody
public ResponseEntity jsonErrorHandle(HttpServletRequest request, GlobalErrorException exception){
ErrorCodeInterface errorCodeInterface = exception.getErrorCode();
return ApiUtils.getResponseBody(errorCodeInterface);
}
}
注:@RestControllerAdvice 与 @ControllerAdvice + @ResponseBody 起到的作用是一样的
注解@ExceptionHandler里面的value的值GlobalErrorException类是我们自定义的异常类。当出现GlobalErrorException异常时就会进入jsonErrorHandle方法进行处理。ErrorCodeInterface是定义的一个错误信息的接口,他的实现类为ErrorCode。语句 ApiUtils.getResponseBody(errorCodeInterface)
根据异常信息获得返回数据对象(ApiResponseBody)。
3.2 容器级别异常处理
@ControllerAdvice注解的异常处理类并不能处理容器级别的异常,例如拦截器Filter中抛出的异常,没有匹配的url请求404错误,请求参数错误403等等。我们可以通过继承BasicErrorController类重写errorHtml或error方法,以达到我们想要的返回结果。还可以通过实现ErrorController接口的方法来实现自定义异常处理,BasicErrorController类也是实现了ErrorController接口,因此这种方式和BasicErrorController类有相似作用。实现起来相对麻烦,本文只讲解继承BasicErrorController类的方式。
自定义错误处理类CustomizeErrorController:
@Controller
public class CustomizeErrorController extends BasicErrorController{
public CustomizeErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers){
super(errorAttributes, errorProperties, errorViewResolvers);
}
@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);
ApiResponseBody responseBody = new ApiResponseBody((int)body.get("status"), (String) body.get("error") + ": " + (String) body.get("message"));
return new ResponseEntity(responseBody, status);
}
}
该类将会覆盖BasicErrorController类起到处理异常的作用。但这里要注意,如果想要保留对Spring boot默认的对浏览器请求的异常处理(也就是根据异常错误状态码返回error文件夹下对应的错误页面),还需新建一个配置文件CustomErrorConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ResourceProperties.class})
public class CustomErrorConfiguration {
private final ServerProperties serverProperties;
private final List<ErrorViewResolver> errorViewResolvers;
public CustomErrorConfiguration(ServerProperties serverProperties, ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
this.serverProperties = serverProperties;
this.errorViewResolvers = (List)errorViewResolversProvider.getIfAvailable();
}
@Bean
public CustomizeErrorController customizeErrorController(ErrorAttributes errorAttributes){
return new CustomizeErrorController(errorAttributes, this.serverProperties.getError(),errorViewResolvers);
}
}
该类的作用相当于类ErrorMvcAutoConfiguration对于BasicErrorController的作用,会有一些初始化配置。
4. 总结
本文主要讲解了Spring boot的默认异常处理和怎样自定义异常处理。对于浏览器请求默认的处理还是很方便实用的,只需在error文件夹下建立对应异常错误状态码的静态资源文件或模版文件。对于要返回Json格式数据的异常处理,提供了两种自定义方式,应根据实际情况选择适合的方案。
本文代码:https://github.com/ByrsH/Spring-Boot-study/tree/master/error-handling