2019独角兽企业重金招聘Python工程师标准>>>
正常的Web应用开发时,需要考虑到应用运行发生异常时或出现错误时如何来被处理,例如捕获必要的异常信息,记录日志方便日后排错,友好的用户响应输出等等。
当然应用发生错误,有可能是应用自身的问题,也有可能是客户端操作的问题。
Spring Boot默认提供了一种错误处理机制。
默认错误处理机制
默认情况下,Spring Boot为两种情况提供了不同的响应方式。
一种是浏览器客户端访问应用发生错误时,一般情况下浏览器默认发送的请求头中Accept: text/html(当然你更改了就另当别论了),所以Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”。
另一种是机器客户端访问应用发送错误时,Spring Boot会响应Json格式内容。这种情况更常见于利用第三方的Http工具请求接口时。
如果看过源码,会发现两种方式输出用到的内容项是一样的,只不过第一种方式用html格式显示,第二种方式使用了Json格式。
Spring Boot提供这个错误处理机制靠自动配置的BasicErrorController类。如果好奇Spring Boot是如何实现这种机制的。可以参看下BasicErrorController源码以及SpringMVC请求映射匹配规则,点这里。
自定义多种错误页面
上述的默认错误处理机制是一种通用的做法,你可能更期望细化一些处理,对于某些错误你可能想特殊对待。Spring Boot提供了一种方式,笔者认为这种方式是“容器级别”的操作。如下:
@Configuration
public class ContainerConfig {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"));
}
}
}
笔者新增了一个配置类,里面定义了一个bean,主要目的在于针对响应码为500的错误,采用笔者自定义的方式处理。如下:
@RestController
public class ExceptionController {
@RequestMapping("/exception")
public void catchException() {
throw new RuntimeException("error occur");
}
@RequestMapping("/500")
public String showServerError() {
return "server error";
}
}
那么浏览器中结果如下:
很明显这种方式依赖于响应状态码进行定制。看到这里你应该会眼熟,还记得web.xml文件里的配置吗?
java.lang.Throwable
/error/500
500
/error/500
401
/error/401
403
/error/403
笔者认为,Spring Boot的默认错误处理机制,包括自定义的错误页面,与原来的web.xml中的error-page配置是相通的,只不过Spring Boot应用默认使用内嵌Servlet容器,web.xml不见了而已。查看源码你会发现,Spring Boot在应用发生错误时会转向"/error"请求,即交由BasicErrorController处理。
覆盖默认的错误处理方式
默认错误处理机制的响应内容格式不一定是你相中的。理由可能如下:
- “Whitelabel Error Page”页面的样式太单调,用户体验不好。
- Json格式的结果字符串不统一,与你配合的前端人员更希望统一格式,好做统一的显示处理。比如笔者之前与前端人员配合时统一指定响应结果格式为“{status:true,msg:"xxx",data:{xxx}}”,但Spring Boot的Json格式是{status:500,message:"error occur",path:"/exception"......}。
那么你可能更期望可以修改默认的处理方式,改变响应内容格式。Spring Boot开发指南上给出了几种方法。
- 自定义一个bean,实现ErrorController接口,那么默认的错误处理机制将不再生效。
- 自定义一个bean,继承BasicErrorController类,使用一部分现成的功能,自己也可以添加新的public方法,使用@RequestMapping及其produces属性指定新的地址映射。
- 自定义一个ErrorAttribute类型的bean,那么还是默认的两种响应方式,只不过改变了内容项而已。
先说下第三种方法,其实查看BasicErrorController源码,响应结果不论是html还是json,内容源都是ErrorAttribute。第三种方法只能改变内容,却改变不了格式,特别是html页面的样式,笔者就不予考虑了。
其实指南上只是轻描淡写了几种方法,没有很好的示例,不知道是不是指南的作者认为Spring Boot默认的错误处理机制已经很适用了。无论第一种还是第二种办法,都需要你看下BasicErrorController的继承体系及实现,因为BasicErrorController也是实现了ErrorController。笔者也是着实费了一番功夫。
采用第一种方式你可以具有完全的控制权,你可以摒弃默认的“Whitelabel Error Page”,指定自己的视图及视图样式,你可以指定响应的Json格式内容等等,因为BasicErrorController不再起作用,可以参考这里。
由于笔者参照了BasicErrorController的源码,感觉第二种方法可能更简便些,所以实现了第二种方法。第二种方法的思路其实就是你可以通过继承,利用BasicErrorController已有的功能,或者进行扩展。
那么如何覆盖默认的处理行为呢(虽然是自定义bean,但因为是继承,没有覆盖的话还是会采用默认的处理行为)?大致有两种思路。
- 按照指南上所述,你可以新建public方法,使用@RequestMapping及其produces属性,例如@RequestMapping(produces="application/json"),那么只要请求头中包含“Accept: application/json”,则会映射到你的方法进行处理。看到这儿你可能会疑惑,why?还是建议你先看下BasicErrorController源码。
- 因为是继承,所以对BasicErrorController默认两种错误处理方式的方法进行override。
笔者希望完全覆盖及可控,所以选择了第二种思路。笔者自定义了MyErrorController,继承于BasicErrorController,注意一定要添加@Controller,不然Spring无法感知自定义的bean,继承于BasicErrorController还是会起作用!具体实现可猛戳这里。
由于笔者使用了自定义的视图,需要添加如下依赖:
org.springframework.boot
spring-boot-starter-thymeleaf
Spring Boot默认视图模板路径为“src/main/resources/templates”。
运行结果如下:
其他
Spring Boot提供的ErrorController是一种全局性的容错机制。你还可以使用SpringMVC提供的@ControllerAdvice。
如字面意思,@ControllerAdvice是切面技术的应用,允许你对Controller中抛出的某个或某些异常进行捕获并响应输出。用法如下:
@ControllerAdvice
public class DefaultExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionHandler.class); //日志记录器
@ExceptionHandler({MissingServletRequestParameterException.class, TypeMismatchException.class, IllegalArgumentException.class, IllegalStateException.class})
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public JsonResult conversionErrorHandler(Exception ex) {
//记录日志
LOGGER.error("参数异常捕获", ex);
return new JsonResult(false, ErrorCode2Msg.getMessage(10004));
}
}
笔者以往的开发习惯,使用@ControllerAdvice捕获应用级别的异常,使用web.xml中的error-page配置处理容器级别的报错。假设定义的过滤器抛出的异常,@ControllerAdvice是无法处理的。
改用Spring Boot后,@ControllerAdvice没有捕获的异常,ErrorController会帮你“捡起来”。