摘要:定制特定異常返回結果根據(jù)官方文檔的例子,可以使用和對特定異常返回特定的結果。下面是用瀏覽器和訪問的結果無輸出注意上方表格的錯誤,產(chǎn)生這個的原因前面已經(jīng)講過。不過需要注意的是,無法通過設定,由或者容器決定里一律是。
github:https://github.com/chanjarste...
參考文檔:
Spring Boot 1.5.4.RELEASE Documentation
Spring framework 4.3.9.RELEASE Documentation
Exception Handling in Spring MVC
默認行為根據(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
也就是說,當發(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返回的結果都是一個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,它不會返回任何結果,原因稍后會有說明。
本章節(jié)代碼在me.chanjar.boot.def,使用DefaultExample運行。
注意:我們必須在application.properties添加server.error.include-stacktrace=always才能夠得到stacktrace。
Spring MVC處理請求的總體流程 分析為何瀏覽器訪問都Whitelabel Error Page 分析為何curl text/plain資源卻沒有返回結果如果你在logback-spring.xml里一樣配置了這么一段:
那么你就能在日志文件里發(fā)現(xiàn)這么一個異常:
... TRACE 13387 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Invoking "org.springframework.boot.autoconfigure.web.BasicErrorController.error" with arguments [org.apache.catalina.core.ApplicationHttpRequest@1408b81] ... TRACE 13387 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Method [org.springframework.boot.autoconfigure.web.BasicErrorController.error] returned [<500 Internal Server Error,{timestamp=Thu Nov 09 13:20:15 CST 2017, status=500, error=Internal Server Error, exception=me.chanjar.exception.SomeException, message=No message available, trace=..., path=/return-text-plain, {}>] ... TRACE 13387 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Error handling return value [type=org.springframework.http.ResponseEntity] [value=<500 Internal Server Error,{timestamp=Thu Nov 09 13:20:15 CST 2017, status=500, error=Internal Server Error, exception=me.chanjar.exception.SomeException, message=No message available, trace=..., path=/return-text-plain, {}>] HandlerMethod details: Controller [org.springframework.boot.autoconfigure.web.BasicErrorController] Method [public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation ...
要理解這個異常是怎么來的,那我們來簡單分析以下Spring MVC的處理過程:
那么這個問題怎么解決呢?我會在自定義ErrorController里說明。
自定義Error頁面前面看到了,Spring Boot針對瀏覽器發(fā)起的請求的error頁面是Whitelabel Error Page,下面講解如何自定義error頁面。
注意2:自定義Error頁面不會影響machine客戶端的輸出結果
方法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所對應的頁面文件即可。
比如在本例里,因為使用的是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訪問相關地址可以看到,返回的json里的出了修改過的屬性,還有添加的屬性:
{ "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信息,解決這個問題有兩個關鍵點:
請求的時候指定Accept頭,避免匹配到BasicErrorController.error方法。比如:curl -H "Accept: text/plain" http://localhost:8080/return-text-plain
提供自定義的ErrorController提供一個path=/error procudes=text/plain的方法。
其實還有另一種方式:提供一個Object->String轉換的HttpMessageConverter,這個方法本文不展開。
下面將如何提供自定義的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定制特定異常返回結果根據(jù)Spring Boot官方文檔的例子,可以使用@ControllerAdvice和@ExceptionHandler對特定異常返回特定的結果。
我們在這里定義了一個新的異常:AnotherException,然后在BarControllerAdvice中對SomeException和AnotherException定義了不同的@ExceptionHandler:
SomeException都返回到controlleradvice/some-ex-error.html上
AnotherException統(tǒng)統(tǒng)返回ResponseEntity
在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 | error(json) | error(json) |
http://localhost:8080/bar/json-a | some-ex-error.html | some-ex-error.html |
http://localhost:8080/bar/json-b | error(json) | 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(White Error Page) | Could not find acceptable representation(無輸出) |
注意上方表格的Could not find acceptable representation錯誤,產(chǎn)生這個的原因前面已經(jīng)講過。
不過需要注意的是流程稍微有點不同,在前面的例子里的流程是這樣的:
訪問url
拋出異常
forward到 /error
BasicErrorController.error方法返回的ResponseEntity沒有辦法轉換成String
本章節(jié)例子的異常是這樣的:
訪問url
拋出異常
被@ExceptionHandler處理
AnotherException的@ExceptionHander返回的ResponseEntity沒有辦法轉換成String,被算作沒有被處理成功
forward到 /error
BasicErrorController.error方法返回的ResponseEntity沒有辦法轉換成String
所以你會發(fā)現(xiàn)如果使用@ExceptionHandler,那就得自己根據(jù)請求頭Accept的不同而輸出不同的結果了,辦法就是定義一個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會獲得不同的結果:
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:
@ResponseStatus(HttpStatus.FORBIDDEN) public class Exception403 extends RuntimeException
在loo/error-406中,我們拋出了異常Exception406:
@ResponseStatus(NOT_ACCEPTABLE) public class Exception406 extends RuntimeException
注意到這兩個異常都有@ResponseStatus注解,這個是注解標明了這個異常所對應的Status Code。
但是在loo/error-600中拋出的SomeException沒有這個注解,而是嘗試在Response.setStatus(600)來達到目的,但結果是失敗的,這是為什么呢?:
@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有這么幾個:
DefaultErrorAttributes,只負責把異常記錄在Request attributes中,name是org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
ExceptionHandlerExceptionResolver,根據(jù)@ExceptionHandler resolve
ResponseStatusExceptionResolver,根據(jù)@ResponseStatus resolve
DefaultHandlerExceptionResolver,負責處理Spring MVC標準異常
Exception403和Exception406都有被ResponseStatusExceptionResolver處理了,而SomeException沒有任何Handler處理,這樣DispatcherServlet就會將這個異常往上拋至到容器處理(見DispatcherServlet#L1243),以Tomcat為例,它在StandardHostValve#L317、StandardHostValve#L345會將Status Code設置成500,然后forward到/error,結果就是BasicErrorController處理時就看到Status Code=500,然后按照500去找error page找不到,就只能返回White error page了。
實際上,從Request的attributes角度來看,交給BasicErrorController處理時,和容器自己處理時,有幾個相關屬性的內(nèi)部情況時這樣的:
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
解決辦法有兩個:
給SomeException添加@ResponseStatus,但是這個方法有兩個局限:
如果這個異常不是你能修改的,比如在第三方的Jar包里
如果@ResponseStatus使用HttpStatus作為參數(shù),但是這個枚舉定義的Status Code數(shù)量有限
使用@ExceptionHandler,不過得注意自己決定view以及status code
第二種解決辦法的例子loo/error-601,對應的代碼:
@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"; }
總結:
沒有被HandlerExceptionResolverresolve到的異常會交給容器處理。已知的實現(xiàn)有(按照順序):
DefaultErrorAttributes,只負責把異常記錄在Request attributes中,name是org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.ERROR
ExceptionHandlerExceptionResolver,根據(jù)@ExceptionHandler resolve
ResponseStatusExceptionResolver,根據(jù)@ResponseStatus resolve
DefaultHandlerExceptionResolver,負責處理Spring MVC標準異常
@ResponseStatus用來規(guī)定異常對應的Status Code,其他異常的Status Code由容器決定,在Tomcat里都認定為500(StandardHostValve#L317、StandardHostValve#L345)
@ExceptionHandler處理的異常不會經(jīng)過BasicErrorController,需要自己決定如何返回頁面,并且設置Status Code(如果不設置就是200)
BasicErrorController會嘗試根據(jù)Status Code找error page,找不到的話就用Whitelabel error page
本章節(jié)代碼在me.chanjar.boot.customstatuserrorpage,使用CustomStatusErrorPageExample運行。
利用ErrorViewResolver來定制錯誤頁面前面講到BasicErrorController會根據(jù)Status Code來跳轉對應的error頁面,其實這個工作是由DefaultErrorViewResolver完成的。
實際上我們也可以提供自己的ErrorViewResolver來定制特定異常的error頁面。
@Component public class SomeExceptionErrorViewResolver implements ErrorViewResolver { @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Mapmodel) { return new ModelAndView("custom-error-view-resolver/some-ex-error", model); } }
不過需要注意的是,無法通過ErrorViewResolver設定Status Code,Status Code由@ResponseStatus或者容器決定(Tomcat里一律是500)。
本章節(jié)代碼在me.chanjar.boot.customerrorviewresolver,使用CustomErrorViewResolverExample運行。
@ExceptionHandler 和 @ControllerAdvice前面的例子中已經(jīng)有了對@ControllerAdvice和@ExceptionHandler的使用,這里只是在做一些補充說明:
@ExceptionHandler配合@ControllerAdvice用時,能夠應用到所有被@ControllerAdvice切到的Controller
@ExceptionHandler在Controller里的時候,就只會對那個Controller生效
最佳實踐前面講了那么多種方式,那么在Spring MVC中處理異常的最佳實踐是什么?在回答這個問題前我先給出一個好的異常處理應該是什么樣子的:
返回的異常信息能夠適配各種Accept,比如Accept:text/html返回html頁面,Accept:application/json返回json。
統(tǒng)一的異常信息schema,且可自定義,比如只包含timestamp、error、message等信息。
能夠自定義部分信息,比如可以自定義error、message的內(nèi)容。
要達成以上目標我們可以采取的方法:
達成第1條:自定義ErrorController,擴展BasicErrorController,支持更多的Accept類型。
達成第2條:自定義ErrorAttributes
達成第3條:
使用@ResponseStatus或ResponseStatusException(since 5.0)
前一種方式不適用時,自定義ErrorAttributes,在里面寫代碼,針對特定異常返回特定信息。推薦使用配置的方式來做,比如配置文件里寫XXXException的message是YYYY。
Spring MVC對于從Controller拋出的異常是不打印到console的,解決辦法是提供一個HandlerExceptionResolver,比如這樣:
@Order(Ordered.HIGHEST_PRECEDENCE) public class ErrorLogger implements HandlerExceptionResolver { private static final Logger LOGGER = LoggerFactory.getLogger(ErrorLogger.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { LOGGER.error("Exception happened at [{}]: {}", request.getRequestURI(), ExceptionUtils.getStackTrace(ex)); return null; } }附錄I
下表列出哪些特性是Spring Boot的,哪些是Spring MVC的:
Feature | Spring Boot | Spring MVC |
---|---|---|
BasicErrorController | Yes | |
ErrorAttributes | Yes | |
ErrorViewResolver | Yes | |
@ControllerAdvice | Yes | |
@ExceptionHandler | Yes | |
@ResponseStatus | Yes | |
HandlerExceptionResolver | Yes |
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67301.html
摘要:的面向的異常遵從通用的異常層次結構。比如以前常用的框架,現(xiàn)在常用的框架包含許多項目,下面挑一些最常用的出來總結一下。狀態(tài)是流程中事件發(fā)生的地點,在流程中通過轉移的方式從一個狀態(tài)到另一個狀態(tài),流程的當前狀況稱為流程數(shù)據(jù)。 如今做Java尤其是web幾乎是避免不了和Spring打交道了,但是Spring是這樣的大而全,新鮮名詞不斷產(chǎn)生,學起來給人一種凌亂的感覺,我就在這里總結一下,理順頭緒...
摘要:的簡稱,運行環(huán)境,為的運行提供了所需環(huán)境。分割字符串,返回一個分割后的字符串數(shù)組。線程安全是線程安全的,而是非線程安全的。迭代器取代了集合框架中的,迭代器允許調(diào)用者在迭代過程中移除元素。 本文分為十九個模塊,分別是:?Java 基礎、容器、多線程、反射、對象拷貝、Java Web 、異常、網(wǎng)絡、設計模式、Spring/Spring MVC、Spring Boot/Spring Clou...
摘要:挺多人咨詢的,異常處理用切面注解去實現(xiàn)去全局異常處理。全局異常處理類,代碼如下代碼解析如下抽象類是用來處理全局錯誤時進行擴展和實現(xiàn)注解標記的切面排序,值越小擁有越高的優(yōu)先級,這里設置優(yōu)先級偏高。 本文內(nèi)容 為什么要全局異常處理? WebFlux REST 全局異常處理實戰(zhàn) 小結 摘錄:只有不斷培養(yǎng)好習慣,同時不斷打破壞習慣,我們的行為舉止才能夠自始至終都是正確的。 一、為什么要全局...
摘要:學習筆記使用很容易創(chuàng)建一個獨立運行運行內(nèi)嵌容器準生產(chǎn)級別的基于框架的項目,使用你可以不用或者只需要很少的配置。異常消息如果這個錯誤是由異常引起的。錯誤發(fā)生時請求的路徑。 Spring-Boot 1.5 學習筆記 使用Spring Boot很容易創(chuàng)建一個獨立運行(運行jar,內(nèi)嵌Servlet容器)、準生產(chǎn)級別的基于Spring框架的項目,使用Spring Boot你可以不用或者只需要很...
閱讀 2524·2023-04-26 02:47
閱讀 3007·2023-04-26 00:42
閱讀 874·2021-10-12 10:12
閱讀 1381·2021-09-29 09:35
閱讀 1697·2021-09-26 09:55
閱讀 485·2019-08-30 14:00
閱讀 1539·2019-08-29 12:57
閱讀 2359·2019-08-28 18:00