姓名:祝雙 ? ? 學(xué)號:16040520067?
文章轉(zhuǎn)載自http://www.importnew.com/27186.html
原文出處:chanjarster
【嵌牛導(dǎo)讀】:可以讓你學(xué)會如何處理這種情況
【嵌牛鼻子】:java
【嵌牛提問】:這些處理方法哪種效率最高
【嵌牛正文】:
默認(rèn)行為
根據(jù)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
也就是說,當(dāng)發(fā)生異常時:
如果請求是從瀏覽器發(fā)送出來的亿卤,那么返回一個Whitelabel Error Page
如果請求是從machine客戶端發(fā)送出來的蝎抽,那么會返回相同信息的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
會發(fā)現(xiàn)FooController和FooRestController返回的結(jié)果都是一個Whitelabel Error Page也就是html熔号。
但是如果你使用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屉更,它不會返回任何結(jié)果余指,原因稍后會有說明洋侨。
本章節(jié)代碼在me.chanjar.boot.def,使用DefaultExample運行坦敌。
注意:我們必須在application.properties添加server.error.include-stacktrace=always才能夠得到stacktrace侣诵。
為何curl text/plain資源無法獲得error
如果你在logback-spring.xml里一樣配置了這么一段:
1
那么你就能在日志文件里發(fā)現(xiàn)這么一個異常:
1
2
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根據(jù)url匹配到了(見AbstractHandlerMethodMapping.lookupHandlerMethod#L341)FooController.returnTextPlan(produces=text/plain)。
方法拋出了異常搭儒,forward到/error穷当。
RequestMappingHandlerMapping根據(jù)url匹配到了(見AbstractHandlerMethodMapping.lookupHandlerMethod#L341)BasicErrorController的兩個方法errorHtml(produces=text/html)和error(produces=null,相當(dāng)于produces=*/*)仗嗦。
因為請求頭Accept: */*膘滨,所以會匹配error方法上(見AbstractHandlerMethodMapping#L352,RequestMappingInfo.compareTo稀拐,ProducesRequestCondition.compareTo)。
error方法返回的是ResponseEntity>丹弱,會被HttpEntityMethodProcessor.handleReturnValue處理德撬。
HttpEntityMethodProcessor進(jìn)入AbstractMessageConverterMethodProcessor.writeWithMessageConverters,發(fā)現(xiàn)請求要求*/*(Accept: */*)躲胳,而能夠產(chǎn)生text/plain(FooController.returnTextPlan produces=text/plain)蜓洪,那它會去找能夠?qū)ap轉(zhuǎn)換成String的HttpMessageConverter(text/plain代表String),結(jié)果是找不到坯苹。
AbstractMessageConverterMethodProcessor拋出HttpMediaTypeNotAcceptableException隆檀。
那么為什么瀏覽器訪問http://localhost:8080/return-text-plain就可以呢?你只需打開瀏覽器的開發(fā)者模式看看請求頭就會發(fā)現(xiàn)Accept:text/html,…,所以在第4步會匹配到BasicErrorController.errorHtml方法恐仑,那結(jié)果自然是沒有問題了泉坐。
那么這個問題怎么解決呢?我會在自定義ErrorController里說明裳仆。
自定義Error頁面
前面看到了腕让,Spring Boot針對瀏覽器發(fā)起的請求的error頁面是Whitelabel Error Page,下面講解如何自定義error頁面歧斟。
注意2:自定義Error頁面不會影響machine客戶端的輸出結(jié)果
方法1
根據(jù)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上。
本章節(jié)代碼在me.chanjar.boot.customdefaulterrorview队橙,使用CustomDefaultErrorViewExample運行坠陈。
方法2
方法2比方法1簡單很多,在Spring官方文檔中沒有說明喘帚。其實只需要提供error View所對應(yīng)的頁面文件即可畅姊。
比如在本例里,因為使用的是Thymeleaf模板引擎吹由,所以在classpath /templates放一個自定義的error.html就能夠自定義error頁面了若未。
本章節(jié)就不提供代碼了,有興趣的你可以自己嘗試倾鲫。
自定義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訪問相關(guān)地址可以看到疯特,返回的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
}
本章節(jié)代碼在me.chanjar.boot.customerrorattributes,使用CustomErrorAttributesExample運行漓雅。
自定義ErrorController
在前面提到了curl http://localhost:8080/return-text-plain得不到error信息录别,解決這個問題有兩個關(guān)鍵點:
請求的時候指定Accept頭朽色,避免匹配到BasicErrorController.error方法。比如:curl -H ‘Accept: text/plain’ http://localhost:8080/return-text-plain
提供自定義的ErrorController组题。
下面將如何提供自定義的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往踢。
本章節(jié)代碼在me.chanjar.boot.customerrorcontroller腾誉,使用CustomErrorControllerExample運行。
ControllerAdvice定制特定異常返回結(jié)果
根據(jù)Spring Boot官方文檔的例子峻呕,可以使用@ControllerAdvice和@ExceptionHandler對特定異常返回特定的結(jié)果利职。
我們在這里定義了一個新的異常:AnotherException,然后在BarControllerAdvice中對SomeException和AnotherException定義了不同的@ExceptionHandler:
SomeException都返回到controlleradvice/some-ex-error.html上
AnotherException統(tǒng)統(tǒng)返回JSON
在BarController中瘦癌,所有*-a都拋出SomeException猪贪,所有*-b都拋出AnotherException。下面是用瀏覽器和curl訪問的結(jié)果:
urlBrowsercurl
http://localhost:8080/bar/html-asome-ex-error.htmlsome-ex-error.html
http://localhost:8080/bar/html-bNo converter found for return value of type: class AnotherExceptionErrorMessageAbstractMessageConverterMethodProcessor#L187error(json)
http://localhost:8080/bar/json-asome-ex-error.htmlsome-ex-error.html
http://localhost:8080/bar/json-bCould not find acceptable representationerror(json)
http://localhost:8080/bar/text-plain-asome-ex-error.htmlsome-ex-error.html
http://localhost:8080/bar/text-plain-bCould not find acceptable representationCould not find acceptable representation
注意上方表格的Could not find acceptable representation錯誤讯私,產(chǎn)生這個的原因和之前為何curl text/plain資源無法獲得error是一樣的:無法將@ExceptionHandler返回的數(shù)據(jù)轉(zhuǎn)換@RequestMapping.produces所要求的格式热押。
所以你會發(fā)現(xiàn)如果使用@ExceptionHandler,那就得自己根據(jù)請求頭Accept的不同而輸出不同的結(jié)果了斤寇,辦法就是定義一個void @ExceptionHandler桶癣,具體見@ExceptionHandler javadoc。
定制不同Status Code的錯誤頁面
Spring Boot 官方文檔提供了一種簡單的根據(jù)不同Status Code跳到不同error頁面的方法娘锁,見這里牙寞。
我們可以將不同的Status Code的頁面放在classpath: public/error或classpath: templates/error目錄下,比如400.html莫秆、5xx.html间雀、400.ftl、5xx.ftl镊屎。
打開瀏覽器訪問以下url會獲得不同的結(jié)果:
urlResult
http://localhost:8080/loo/error-403static resource: public/error/403.html
http://localhost:8080/loo/error-406thymeleaf view: templates/error/406.html
http://localhost:8080/loo/error-600Whitelabel error page
http://localhost:8080/loo/error-601thymeleaf 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)
publicclassException403extendsRuntimeException
在loo/error-406中用狱,我們拋出了異常Exception406:
1
2
@ResponseStatus(NOT_ACCEPTABLE)
publicclassException406extendsRuntimeException
注意到這兩個異常都有@ResponseStatus注解萎庭,這個是注解標(biāo)明了這個異常所對應(yīng)的Status Code。 但是在loo/error-600中拋出的SomeException沒有這個注解齿拂,而是嘗試在Response.setStatus(600)來達(dá)到目的,但結(jié)果是失敗的肴敛,這是為什么呢署海?:
1
2
3
4
5
6
@RequestMapping("/error-600")
publicString error600(HttpServletRequest request, HttpServletResponse response)throwsSomeException {
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE,600);
response.setStatus(600);
thrownewSomeException();
}
要了解為什么就需要知道Spring MVC對于異常的處理機制吗购,下面簡單講解一下:
Spring MVC處理異常的地方在DispatcherServlet.processHandlerException,這個方法會利用HandlerExceptionResolver來看異常應(yīng)該返回什么ModelAndView砸狞。
目前已知的HandlerExceptionResolver有這么幾個:
DefaultErrorAttributes捻勉,只負(fù)責(zé)把異常記錄在Request attributes中,name是org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
ExceptionHandlerExceptionResolver刀森,根據(jù)@ExceptionHandler resolve
ResponseStatusExceptionResolver踱启,根據(jù)@ResponseStatus resolve
DefaultHandlerExceptionResolver,負(fù)責(zé)處理Spring MVC標(biāo)準(zhǔn)異常
Exception403和Exception406都有被ResponseStatusExceptionResolver處理了研底,而SomeException沒有任何Handler處理埠偿,這樣DispatcherServlet就會將這個異常往上拋至到容器處理(見DispatcherServlet#L1243),以Tomcat為例榜晦,它在StandardHostValve#L317冠蒋、StandardHostValve#L345會將Status Code設(shè)置成500,然后跳轉(zhuǎn)到/error乾胶,結(jié)果就是BasicErrorController處理時就看到Status Code=500抖剿,然后按照500去找error page找不到,就只能返回White error page了识窿。
實際上斩郎,從Request的attributes角度來看,交給BasicErrorController處理時喻频,和容器自己處理時缩宜,有幾個相關(guān)屬性的內(nèi)部情況時這樣的:
Attribute nameWhen throw up to TomcatHandled by HandlerExceptionResolver
DefaultErrorAttributes.ERRORHas valueHas Value
DispatcherServlet.EXCEPTIONNo valueHas Value
javax.servlet.error.exceptionHas valueNo Value
PS. DefaultErrorAttributes.ERROR = org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
PS. DispatcherServlet.EXCEPTION = org.springframework.web.servlet.DispatcherServlet.EXCEPTION
解決辦法有兩個:
1.給SomeException添加@ResponseStatus,但是這個方法有兩個局限:
如果這個異常不是你能修改的半抱,比如在第三方的Jar包里
如果@ResponseStatus使用HttpStatus作為參數(shù)脓恕,但是這個枚舉定義的Status Code數(shù)量有限
2. 使用@ExceptionHandler,不過得注意自己決定view以及status code
第二種解決辦法的例子loo/error-601窿侈,對應(yīng)的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/error-601")
publicString error601(HttpServletRequest request, HttpServletResponse response)throwsAnotherException {
thrownewAnotherException();
}
@ExceptionHandler(AnotherException.class)
String handleAnotherException(HttpServletRequest request, HttpServletResponse response, Model model)
throwsIOException {
// 需要設(shè)置Status Code炼幔,否則響應(yīng)結(jié)果會是200
response.setStatus(601);
model.addAllAttributes(errorAttributes.getErrorAttributes(newServletRequestAttributes(request),true));
return"error/6xx";
}
總結(jié):
1. 沒有被HandlerExceptionResolverresolve到的異常會交給容器處理。已知的實現(xiàn)有(按照順序):
DefaultErrorAttributes史简,只負(fù)責(zé)把異常記錄在Request attributes中乃秀,name是org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
ExceptionHandlerExceptionResolver,根據(jù)@ExceptionHandler resolve
ResponseStatusExceptionResolver圆兵,根據(jù)@ResponseStatus resolve
DefaultHandlerExceptionResolver跺讯,負(fù)責(zé)處理Spring MVC標(biāo)準(zhǔn)異常
2. @ResponseStatus用來規(guī)定異常對應(yīng)的Status Code,其他異常的Status Code由容器決定殉农,在Tomcat里都認(rèn)定為500(StandardHostValve#L317刀脏、StandardHostValve#L345)
3. @ExceptionHandler處理的異常不會經(jīng)過BasicErrorController,需要自己決定如何返回頁面超凳,并且設(shè)置Status Code(如果不設(shè)置就是200)
4. BasicErrorController會嘗試根據(jù)Status Code找error page愈污,找不到的話就用Whitelabel error page
本章節(jié)代碼在me.chanjar.boot.customstatuserrorpage耀态,使用CustomStatusErrorPageExample運行。
利用ErrorViewResolver來定制錯誤頁面
前面講到BasicErrorController會根據(jù)Status Code來跳轉(zhuǎn)對應(yīng)的error頁面暂雹,其實這個工作是由DefaultErrorViewResolver完成的首装。
實際上我們也可以提供自己的ErrorViewResolver來定制特定異常的error頁面。
1
2
3
4
5
6
7
8
9
@Component
publicclassSomeExceptionErrorViewResolverimplementsErrorViewResolver {
@Override
publicModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) {
returnnewModelAndView("custom-error-view-resolver/some-ex-error", model);
}
}
不過需要注意的是杭跪,無法通過ErrorViewResolver設(shè)定Status Code仙逻,Status Code由@ResponseStatus或者容器決定(Tomcat里一律是500)。
本章節(jié)代碼在me.chanjar.boot.customerrorviewresolver涧尿,使用CustomErrorViewResolverExample運行系奉。
@ExceptionHandler 和 @ControllerAdvice
前面的例子中已經(jīng)有了對@ControllerAdvice和@ExceptionHandler的使用,這里只是在做一些補充說明:
@ExceptionHandler配合@ControllerAdvice用時现斋,能夠應(yīng)用到所有被@ControllerAdvice切到的Controller
@ExceptionHandler在Controller里的時候喜最,就只會對那個Controller生效