默认行为
根据Spring Boot官方文档的说法:
For machine clients it will produce a JSON response with details of the error, the HTTP status and the exception message. For browser clients there is a ‘whitelabel’ error view that renders the same data in HTML format
也就是说,当发生异常时:
- 如果请求是从浏览器发送出来的,那么返回一个Whitelabel Error Page
- 如果请求是从machine客户端发送出来的,那么会返回相同信息的json
你可以在浏览器中依次访问以下地址:
- http://localhost:8080/return-model-and-view
- http://localhost:8080/return-view-name
- http://localhost:8080/return-view
- http://localhost:8080/return-text-plain
- http://localhost:8080/return-json-1
- http://localhost:8080/return-json-2
会发现FooController和FooRestController返回的结果都是一个Whitelabel Error Page也就是html。
但是如果你使用curl访问上述地址,那么返回的都是如下的json:
{ "timestamp": 1498886969426, "status": 500, "error": "Internal Server Error", "exception": "me.chanjar.exception.SomeException", "message": "...", "trace": "...", "path": "..." }
但是有一个URL除外:http://localhost:8080/return-text-plain,它不会返回任何结果,原因稍后会有说明。
本章节代码在me.chanjar.boot.def,使用DefaultExample运行。
注意:我们必须在application.properties添加server.error.include-stacktrace=always才能够得到stacktrace。
为何curl text/plain资源无法获得error
如果你在logback-spring.xml里一样配置了这么一段:
<logger name="org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod" level="TRACE"/>
那么你就能在日志文件里发现这么一个异常:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation ...
要理解这个异常是怎么来的,那我们来简单分析以下Spring MVC的处理过程:
- curl http://localhost:8080/return-text-plain,会隐含一个请求头Accept: */*,会匹配到FooController.returnTextPlain(produces=text/plain)方法,注意:如果请求头不是Accept: */*或Accept: text/plain,那么是匹配不到FooController.returnTextPlain的。
- RequestMappingHandlerMapping根据url匹配到了(见AbstractHandlerMethodMapping.lookupHandlerMethod#L341)FooController.returnTextPlan(produces=text/plain)。
- 方法抛出了异常,forward到/error。
- RequestMappingHandlerMapping根据url匹配到了(见AbstractHandlerMethodMapping.lookupHandlerMethod#L341)BasicErrorController的两个方法errorHtml(produces=text/html)和error(produces=null,相当于produces=*/*)。
- 因为请求头Accept: */*,所以会匹配error方法上(见AbstractHandlerMethodMapping#L352,RequestMappingInfo.compareTo,ProducesRequestCondition.compareTo)。
- error方法返回的是ResponseEntity<Map<String, Object>>,会被HttpEntityMethodProcessor.handleReturnValue处理。
- HttpEntityMethodProcessor进入AbstractMessageConverterMethodProcessor.writeWithMessageConverters,发现请求要求*/*(Accept: */*),而能够产生text/plain(FooController.returnTextPlan produces=text/plain),那它会去找能够将Map转换成String的HttpMessageConverter(text/plain代表String),结果是找不到。
- AbstractMessageConverterMethodProcessor抛出HttpMediaTypeNotAcceptableException。
那么为什么浏览器访问http://localhost:8080/return-text-plain就可以呢?你只需打开浏览器的开发者模式看看请求头就会发现Accept:text/html,…,所以在第4步会匹配到BasicErrorController.errorHtml方法,那结果自然是没有问题了。
那么这个问题怎么解决呢?我会在自定义ErrorController里说明。
自定义Error页面
前面看到了,Spring Boot针对浏览器发起的请求的error页面是Whitelabel Error Page,下面讲解如何自定义error页面。
注意2:自定义Error页面不会影响machine客户端的输出结果
方法1
根据Spring Boot官方文档,如果想要定制