根據Spring Boot官方文檔的說法:html
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 formatjava
也就是說,當發生異常時:git
你能夠在瀏覽器中依次訪問如下地址:github
會發現FooController和FooRestController返回的結果都是一個Whitelabel Error Page也就是html。web
可是若是你使用curl訪問上述地址,那麼返回的都是以下的json:面試
1
2
3
4
5
6
7
8
9
|
{
"timestamp"
:
1498886969426
,
"status"
:
500
,
"error"
:
"Internal Server Error"
,
"exception"
:
"me.chanjar.exception.SomeException"
,
"message"
:
"..."
,
"trace"
:
"..."
,
"path"
:
"..."
}
|
可是有一個URL除外:http://localhost:8080/return-text-plain,它不會返回任何結果,緣由稍後會有說明。spring
本章節代碼在me.chanjar.boot.def,使用DefaultExample運行。json
注意:咱們必須在application.properties添加server.error.include-stacktrace=always纔可以獲得stacktrace。瀏覽器
若是你在logback-spring.xml裏同樣配置了這麼一段:spring-mvc
1
|
<logger name=
"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod"
level=
"TRACE"
/>
|
那麼你就能在日誌文件裏發現這麼一個異常:
1
2
|
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
...
|
要理解這個異常是怎麼來的,那咱們來簡單分析如下Spring MVC的處理過程:
那麼爲何瀏覽器訪問http://localhost:8080/return-text-plain就能夠呢?你只需打開瀏覽器的開發者模式看看請求頭就會發現Accept:text/html,…,因此在第4步會匹配到BasicErrorController.errorHtml方法,那結果天然是沒有問題了。
那麼這個問題怎麼解決呢?我會在自定義ErrorController裏說明。
前面看到了,Spring Boot針對瀏覽器發起的請求的error頁面是Whitelabel Error Page,下面講解如何自定義error頁面。
注意2:自定義Error頁面不會影響machine客戶端的輸出結果
根據Spring Boot官方文檔,若是想要定製這個頁面只須要:
to customize it just add a View that resolves to ‘error’
這句話講的不是很明白,其實只要看ErrorMvcAutoConfiguration.WhitelabelErrorViewConfiguration的代碼就知道,只需註冊一個名字叫作error的View類型的Bean就好了。
本例的CustomDefaultErrorViewConfiguration註冊將error頁面改到了templates/custom-error-page/error.html上。
本章節代碼在me.chanjar.boot.customdefaulterrorview,使用CustomDefaultErrorViewExample運行。
方法2比方法1簡單不少,在Spring官方文檔中沒有說明。其實只須要提供error View所對應的頁面文件便可。
好比在本例裏,由於使用的是Thymeleaf模板引擎,因此在classpath /templates放一個自定義的error.html就可以自定義error頁面了。
本章節就不提供代碼了,有興趣的你能夠本身嘗試。
前面看到了不論error頁面仍是error json,可以獲得的屬性就只有:timestamp、status、error、exception、message、trace、path。
若是你想自定義這些屬性,能夠如Spring Boot官方文檔所說的:
simply add a bean of type ErrorAttributes to use the existing mechanism but replace the contents
在ErrorMvcAutoConfiguration.errorAttributes提供了DefaultErrorAttributes,咱們也能夠參照這個提供一個本身的CustomErrorAttributes覆蓋掉它。
若是使用curl訪問相關地址能夠看到,返回的json裏的出了修改過的屬性,還有添加的屬性:
1
2
3
4
5
6
7
8
9
10
|
{
"exception"
:
"customized exception"
,
"add-attribute"
:
"add-attribute"
,
"path"
:
"customized path"
,
"trace"
:
"customized trace"
,
"error"
:
"customized error"
,
"message"
:
"customized message"
,
"timestamp"
:
1498892609326
,
"status"
:
100
}
|
本章節代碼在me.chanjar.boot.customerrorattributes,使用CustomErrorAttributesExample運行。
在前面提到了curl http://localhost:8080/return-text-plain得不到error信息,解決這個問題有兩個關鍵點:
下面將如何提供自定義的ErrorController。按照Spring Boot官方文檔的說法:
To do that just extend BasicErrorController and add a public method with a @RequestMapping that has a produces attribute, and create a bean of your new type.
因此咱們提供了一個CustomErrorController,而且經過CustomErrorControllerConfiguration將其註冊爲Bean。
本章節代碼在me.chanjar.boot.customerrorcontroller,使用CustomErrorControllerExample運行。
根據Spring Boot官方文檔的例子,可使用@ControllerAdvice和@ExceptionHandler對特定異常返回特定的結果。
咱們在這裏定義了一個新的異常:AnotherException,而後在BarControllerAdvice中對SomeException和AnotherException定義了不一樣的@ExceptionHandler:
在BarController中,全部*-a都拋出SomeException,全部*-b都拋出AnotherException。下面是用瀏覽器和curl訪問的結果:
url | Browser | curl |
---|---|---|
http://localhost:8080/bar/html-a | some-ex-error.html | some-ex-error.html |
http://localhost:8080/bar/html-b | No converter found for return value of type: class AnotherExceptionErrorMessageAbstractMessageConverterMethodProcessor#L187 | error(json) |
http://localhost:8080/bar/json-a | some-ex-error.html | some-ex-error.html |
http://localhost:8080/bar/json-b | Could not find acceptable representation | error(json) |
http://localhost:8080/bar/text-plain-a | some-ex-error.html | some-ex-error.html |
http://localhost:8080/bar/text-plain-b | Could not find acceptable representation | Could not find acceptable representation |
注意上方表格的Could not find acceptable representation錯誤,產生這個的緣由和以前爲什麼curl text/plain資源沒法得到error是同樣的:沒法將@ExceptionHandler返回的數據轉換@RequestMapping.produces所要求的格式。
因此你會發現若是使用@ExceptionHandler,那就得本身根據請求頭Accept的不一樣而輸出不一樣的結果了,辦法就是定義一個void @ExceptionHandler,具體見@ExceptionHandler javadoc。
Spring Boot 官方文檔提供了一種簡單的根據不一樣Status Code跳到不一樣error頁面的方法,見這裏。
咱們能夠將不一樣的Status Code的頁面放在classpath: public/error或classpath: templates/error目錄下,好比400.html、5xx.html、400.ftl、5xx.ftl。
打開瀏覽器訪問如下url會得到不一樣的結果:
url | Result |
---|---|
http://localhost:8080/loo/error-403 | static resource: public/error/403.html |
http://localhost:8080/loo/error-406 | thymeleaf view: templates/error/406.html |
http://localhost:8080/loo/error-600 | Whitelabel error page |
http://localhost:8080/loo/error-601 | thymeleaf view: templates/error/6xx.html |
注意/loo/error-600返回的是Whitelabel error page,可是/loo/error-403和loo/error-406可以返回咱們指望的錯誤頁面,這是爲何?先來看看代碼。
在loo/error-403中,咱們拋出了異常Exception403:
1
2
|
@ResponseStatus
(HttpStatus.FORBIDDEN)
public
class
Exception403
extends
RuntimeException
|
在loo/error-406中,咱們拋出了異常Exception406:
1
2
|
@ResponseStatus
(NOT_ACCEPTABLE)
public
class
Exception406
extends
RuntimeException
|
注意到這兩個異常都有@ResponseStatus註解,這個是註解標明瞭這個異常所對應的Status Code。 可是在loo/error-600中拋出的SomeException沒有這個註解,而是嘗試在Response.setStatus(600)來達到目的,但結果是失敗的,這是爲何呢?:
1
2
3
4
5
6
|
@RequestMapping
(
"/error-600"
)
public
String error600(HttpServletRequest request, HttpServletResponse response)
throws
SomeException {
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE,
600
);
response.setStatus(
600
);
throw
new
SomeException();
}
|
要了解爲何就須要知道Spring MVC對於異常的處理機制,下面簡單講解一下:
Spring MVC處理異常的地方在DispatcherServlet.processHandlerException,這個方法會利用HandlerExceptionResolver來看異常應該返回什麼ModelAndView。
目前已知的HandlerExceptionResolver有這麼幾個:
Exception403和Exception406都有被ResponseStatusExceptionResolver處理了,而SomeException沒有任何Handler處理,這樣DispatcherServlet就會將這個異常往上拋至到容器處理(見DispatcherServlet#L1243),以Tomcat爲例,它在StandardHostValve#L31七、StandardHostValve#L345會將Status Code設置成500,而後跳轉到/error,結果就是BasicErrorController處理時就看到Status Code=500,而後按照500去找error page找不到,就只能返回White error page了。
具備1-5工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加羣。在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加羣。若是沒有工做經驗,但基礎很是紮實,對java工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的能夠加羣。java架構羣:591240817 一塊兒交流。
實際上,從Request的attributes角度來看,交給BasicErrorController處理時,和容器本身處理時,有幾個相關屬性的內部狀況時這樣的:
Attribute name | When throw up to Tomcat | Handled by HandlerExceptionResolver |
---|---|---|
DefaultErrorAttributes.ERROR |
Has value | Has Value |
DispatcherServlet.EXCEPTION |
No value | Has Value |
javax.servlet.error.exception |
Has value | No Value |
PS. DefaultErrorAttributes.ERROR = org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
PS. DispatcherServlet.EXCEPTION = org.springframework.web.servlet.DispatcherServlet.EXCEPTION
解決辦法有兩個:
1.給SomeException添加@ResponseStatus,可是這個方法有兩個侷限:
2. 使用@ExceptionHandler,不過得注意本身決定view以及status code
第二種解決辦法的例子loo/error-601,對應的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@RequestMapping
(
"/error-601"
)
public
String error601(HttpServletRequest request, HttpServletResponse response)
throws
AnotherException {
throw
new
AnotherException();
}
@ExceptionHandler
(AnotherException.
class
)
String handleAnotherException(HttpServletRequest request, HttpServletResponse response, Model model)
throws
IOException {
// 須要設置Status Code,不然響應結果會是200
response.setStatus(
601
);
model.addAllAttributes(errorAttributes.getErrorAttributes(
new
ServletRequestAttributes(request),
true
));
return
"error/6xx"
;
}
|
1. 沒有被HandlerExceptionResolverresolve到的異常會交給容器處理。已知的實現有(按照順序):
2. @ResponseStatus用來規定異常對應的Status Code,其餘異常的Status Code由容器決定,在Tomcat裏都認定爲500(StandardHostValve#L31七、StandardHostValve#L345)
3. @ExceptionHandler處理的異常不會通過BasicErrorController,須要本身決定如何返回頁面,而且設置Status Code(若是不設置就是200)
4. BasicErrorController會嘗試根據Status Code找error page,找不到的話就用Whitelabel error page
本章節代碼在me.chanjar.boot.customstatuserrorpage,使用CustomStatusErrorPageExample運行。
前面講到BasicErrorController會根據Status Code來跳轉對應的error頁面,其實這個工做是由DefaultErrorViewResolver完成的。
實際上咱們也能夠提供本身的ErrorViewResolver來定製特定異常的error頁面。
1
2
3
4
5
6
7
8
9
|
@Component
public
class
SomeExceptionErrorViewResolver
implements
ErrorViewResolver {
@Override
public
ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
return
new
ModelAndView(
"custom-error-view-resolver/some-ex-error"
, model);
}
}
|
不過須要注意的是,沒法經過ErrorViewResolver設定Status Code,Status Code由@ResponseStatus或者容器決定(Tomcat裏一概是500)。
本章節代碼在me.chanjar.boot.customerrorviewresolver,使用CustomErrorViewResolverExample運行。
前面的例子中已經有了對@ControllerAdvice和@ExceptionHandler的使用,這裏只是在作一些補充說明:
下表列出哪些特性是Spring Boot的,哪些是Spring MVC的:
Feature | Spring Boot | Spring MVC |
---|---|---|
BasicErrorController | Yes | |
ErrorAttributes | Yes | |
ErrorViewResolver | Yes | |
@ControllerAdvice | Yes | |
@ExceptionHandler | Yes | |
@ResponseStatus | Yes | |
HandlerExceptionResolver | Yes |
能夠關注個人微信公衆號得到更多學習交流的機會