本文首先將會回顧spring 5之前的springmvc異常處理機(jī)制,然后主要講解spring boot 2 webflux的全局異常處理機(jī)制。
springmvc的異常處理
spring 統(tǒng)一異常處理有 3 種方式,分別為:
- 使用 @exceptionhandler 注解
- 實(shí)現(xiàn) handlerexceptionresolver 接口
- 使用 @controlleradvice 注解
使用 @exceptionhandler 注解
用于局部方法捕獲,與拋出異常的方法處于同一個controller類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@controller public class buzcontroller { @exceptionhandler ({nullpointerexception. class }) public string exception(nullpointerexception e) { system.out.println(e.getmessage()); e.printstacktrace(); return "null pointer exception" ; } @requestmapping ( "test" ) public void test() { throw new nullpointerexception( "出錯了!" ); } } |
如上的代碼實(shí)現(xiàn),針對 buzcontroller 拋出的 nullpointerexception 異常,將會捕獲局部異常,返回指定的內(nèi)容。
實(shí)現(xiàn) handlerexceptionresolver 接口
通過實(shí)現(xiàn) handlerexceptionresolver 接口,定義全局異常:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@component public class custommvcexceptionhandler implements handlerexceptionresolver { private objectmapper objectmapper; public custommvcexceptionhandler() { objectmapper = new objectmapper(); } @override public modelandview resolveexception(httpservletrequest request, httpservletresponse response, object o, exception ex) { response.setstatus( 200 ); response.setcontenttype(mediatype.application_json_value); response.setcharacterencoding( "utf-8" ); response.setheader( "cache-control" , "no-cache, must-revalidate" ); map<string, object> map = new hashmap<>(); if (ex instanceof nullpointerexception) { map.put( "code" , responsecode.np_exception); } else if (ex instanceof indexoutofboundsexception) { map.put( "code" , responsecode.index_out_of_bounds_exception); } else { map.put( "code" , responsecode.catch_exception); } try { map.put( "data" , ex.getmessage()); response.getwriter().write(objectmapper.writevalueasstring(map)); } catch (exception e) { e.printstacktrace(); } return new modelandview(); } } |
如上為示例的使用方式,我們可以根據(jù)各種異常定制錯誤的響應(yīng)。
使用 @controlleradvice 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@controlleradvice public class exceptioncontroller { @exceptionhandler (runtimeexception. class ) public modelandview handlerruntimeexception(runtimeexception ex) { if (ex instanceof maxuploadsizeexceededexception) { return new modelandview( "error" ).addobject( "msg" , "文件太大!" ); } return new modelandview( "error" ).addobject( "msg" , "未知錯誤:" + ex); } @exceptionhandler (exception. class ) public modelandview handlermaxuploadsizeexceededexception(exception ex) { if (ex != null ) { return new modelandview( "error" ).addobject( "msg" , ex); } return new modelandview( "error" ).addobject( "msg" , "未知錯誤:" + ex); } } |
和第一種方式的區(qū)別在于, exceptionhandler 的定義和異常捕獲可以擴(kuò)展到全局。
spring 5 webflux的異常處理
webflux支持mvc的注解,是一個非常便利的功能,相比較于routefunction,自動掃描注冊比較省事。異常處理可以沿用exceptionhandler。如下的全局異常處理對于restcontroller依然生效。
1
2
3
4
5
6
7
8
9
10
11
|
@restcontrolleradvice public class customexceptionhandler { private final log logger = logfactory.getlog(getclass()); @exceptionhandler (exception. class ) @responsestatus (code = httpstatus.ok) public errorcode handlecustomexception(exception e) { logger.error(e.getmessage()); return new errorcode( "e" , "error" ); } } |
webflux示例
webflux提供了一套函數(shù)式接口,可以用來實(shí)現(xiàn)類似mvc的效果。我們先接觸兩個常用的。
controller定義對request的處理邏輯的方式,主要有方面:
- 方法定義處理邏輯;
- 然后用@requestmapping注解定義好這個方法對什么樣url進(jìn)行響應(yīng)。
在webflux的函數(shù)式開發(fā)模式中,我們用handlerfunction和routerfunction來實(shí)現(xiàn)上邊這兩點(diǎn)。
handlerfunction
handlerfunction 相當(dāng)于controller中的具體處理方法,輸入為請求,輸出為裝在mono中的響應(yīng):
1
|
mono<t> handle(serverrequest var1); |
在webflux中,請求和響應(yīng)不再是webmvc中的servletrequest和servletresponse,而是serverrequest和serverresponse。后者是在響應(yīng)式編程中使用的接口,它們提供了對非阻塞和回壓特性的支持,以及http消息體與響應(yīng)式類型mono和flux的轉(zhuǎn)換方法。
1
2
3
4
5
6
7
|
@component public class timehandler { public mono<serverresponse> gettime(serverrequest serverrequest) { string timetype = serverrequest.queryparam( "type" ).get(); //return ... } } |
如上定義了一個 timehandler ,根據(jù)請求的參數(shù)返回當(dāng)前時間。
routerfunction
routerfunction ,顧名思義,路由,相當(dāng)于 @requestmapping ,用來判斷什么樣的url映射到那個具體的 handlerfunction 。輸入為請求,輸出為mono中的 handlerfunction :
1
|
mono<handlerfunction<t>> route(serverrequest var1); |
針對我們要對外提供的功能,我們定義一個route。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@configuration public class routerconfig { private final timehandler timehandler; @autowired public routerconfig(timehandler timehandler) { this .timehandler = timehandler; } @bean public routerfunction<serverresponse> timerrouter() { return route(get( "/time" ), req -> timehandler.gettime(req)); } } |
可以看到訪問/time的get請求,將會由 timehandler::gettime 處理。
功能級別處理異常
如果我們在沒有指定時間類型(type)的情況下調(diào)用相同的請求地址,例如/time,它將拋出異常。
mono和flux apis內(nèi)置了兩個關(guān)鍵操作符,用于處理功能級別上的錯誤。
使用onerrorresume處理錯誤
還可以使用onerrorresume處理錯誤,fallback方法定義如下:
1
|
mono<t> onerrorresume(function<? super throwable, ? extends mono<? extends t>> fallback); |
當(dāng)出現(xiàn)錯誤時,我們使用fallback方法執(zhí)行替代路徑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@component public class timehandler { public mono<serverresponse> gettime(serverrequest serverrequest) { string timetype = serverrequest.queryparam( "time" ).orelse( "now" ); return gettimebytype(timetype).flatmap(s -> serverresponse.ok() .contenttype(mediatype.text_plain).syncbody(s)) .onerrorresume(e -> mono.just( "error: " + e.getmessage()).flatmap(s -> serverresponse.ok().contenttype(mediatype.text_plain).syncbody(s))); } private mono<string> gettimebytype(string timetype) { string type = optional.ofnullable(timetype).orelse( "now" ); switch (type) { case "now" : return mono.just( "now is " + new simpledateformat( "hh:mm:ss" ).format( new date())); case "today" : return mono.just( "today is " + new simpledateformat( "yyyy-mm-dd" ).format( new date())); default : return mono.empty(); } } } |
在如上的實(shí)現(xiàn)中,每當(dāng) gettimebytype() 拋出異常時,將會執(zhí)行我們定義的 fallback 方法。除此之外,我們還可以捕獲、包裝和重新拋出異常,例如作為自定義業(yè)務(wù)異常:
1
2
3
4
5
6
7
|
public mono<serverresponse> gettime(serverrequest serverrequest) { string timetype = serverrequest.queryparam( "time" ).orelse( "now" ); return serverresponse.ok() .body(gettimebytype(timetype) .onerrorresume(e -> mono.error( new serverexception( new errorcode(httpstatus.bad_request.value(), "timetype is required" , e.getmessage())))), string. class ); } |
使用onerrorreturn處理錯誤
每當(dāng)發(fā)生錯誤時,我們可以使用 onerrorreturn() 返回靜態(tài)默認(rèn)值:
1
2
3
4
5
6
7
|
public mono<serverresponse> getdate(serverrequest serverrequest) { string timetype = serverrequest.queryparam( "time" ).get(); return gettimebytype(timetype) .onerrorreturn( "today is " + new simpledateformat( "yyyy-mm-dd" ).format( new date())) .flatmap(s -> serverresponse.ok() .contenttype(mediatype.text_plain).syncbody(s)); } |
全局異常處理
如上的配置是在方法的級別處理異常,如同對注解的controller全局異常處理一樣,webflux的函數(shù)式開發(fā)模式也可以進(jìn)行全局異常處理。要做到這一點(diǎn),我們只需要自定義全局錯誤響應(yīng)屬性,并且實(shí)現(xiàn)全局錯誤處理邏輯。
我們的處理程序拋出的異常將自動轉(zhuǎn)換為http狀態(tài)和json錯誤正文。要自定義這些,我們可以簡單地?cái)U(kuò)展 defaulterrorattributes 類并覆蓋其 geterrorattributes() 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@component public class globalerrorattributes extends defaulterrorattributes { public globalerrorattributes() { super ( false ); } @override public map<string, object> geterrorattributes(serverrequest request, boolean includestacktrace) { return assembleerror(request); } private map<string, object> assembleerror(serverrequest request) { map<string, object> errorattributes = new linkedhashmap<>(); throwable error = geterror(request); if (error instanceof serverexception) { errorattributes.put( "code" , ((serverexception) error).getcode().getcode()); errorattributes.put( "data" , error.getmessage()); } else { errorattributes.put( "code" , httpstatus.internal_server_error); errorattributes.put( "data" , "internal server error" ); } return errorattributes; } //...有省略 } |
如上的實(shí)現(xiàn)中,我們對 serverexception 進(jìn)行了特別處理,根據(jù)傳入的 errorcode 對象構(gòu)造對應(yīng)的響應(yīng)。
接下來,讓我們實(shí)現(xiàn)全局錯誤處理程序。為此,spring提供了一個方便的 abstracterrorwebexceptionhandler 類,供我們在處理全局錯誤時進(jìn)行擴(kuò)展和實(shí)現(xiàn):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@component @order (- 2 ) public class globalerrorwebexceptionhandler extends abstracterrorwebexceptionhandler { //構(gòu)造函數(shù) @override protected routerfunction<serverresponse> getroutingfunction( final errorattributes errorattributes) { return routerfunctions.route(requestpredicates.all(), this ::rendererrorresponse); } private mono<serverresponse> rendererrorresponse( final serverrequest request) { final map<string, object> errorpropertiesmap = geterrorattributes(request, true ); return serverresponse.status(httpstatus.ok) .contenttype(mediatype.application_json_utf8) .body(bodyinserters.fromobject(errorpropertiesmap)); } } |
這里將全局錯誤處理程序的順序設(shè)置為-2。這是為了讓它比 @order(-1) 注冊的 defaulterrorwebexceptionhandler 處理程序更高的優(yōu)先級。
該errorattributes對象將是我們在網(wǎng)絡(luò)異常處理程序的構(gòu)造函數(shù)傳遞一個的精確副本。理想情況下,這應(yīng)該是我們自定義的error attributes類。然后,我們清楚地表明我們想要將所有錯誤處理請求路由到rendererrorresponse()方法。最后,我們獲取錯誤屬性并將它們插入服務(wù)器響應(yīng)主體中。
然后,它會生成一個json響應(yīng),其中包含錯誤,http狀態(tài)和計(jì)算機(jī)客戶端異常消息的詳細(xì)信息。對于瀏覽器客戶端,它有一個whitelabel錯誤處理程序,它以html格式呈現(xiàn)相同的數(shù)據(jù)。當(dāng)然,這可以是定制的。
小結(jié)
本文首先講了spring 5之前的springmvc異常處理機(jī)制,springmvc統(tǒng)一異常處理有 3 種方式:使用 @exceptionhandler 注解、實(shí)現(xiàn) handlerexceptionresolver 接口、使用 @controlleradvice 注解;然后通過webflux的函數(shù)式接口構(gòu)建web應(yīng)用,講解spring boot 2 webflux的函數(shù)級別和全局異常處理機(jī)制(對于spring webmvc風(fēng)格,基于注解的方式編寫響應(yīng)式的web服務(wù),仍然可以通過springmvc統(tǒng)一異常處理實(shí)現(xiàn))。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://blueskykong.com/2018/12/18/webflux-error/