好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

详解Spring Boot2 Webflux的全局异常处理

本文首先将会回顾spring 5之前的springmvc异常处理机制,然后主要讲解spring boot 2 webflux的全局异常处理机制。

springmvc的异常处理

spring 统一异常处理有 3 种方式,分别为:

使用 @exceptionhandler 注解 实现 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( "出错了!" );

   }

}

如上的代码实现,针对 buzcontroller 抛出的 nullpointerexception 异常,将会捕获局部异常,返回指定的内容。

实现 handlerexceptionresolver 接口

通过实现 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();

   }

}

如上为示例的使用方式,我们可以根据各种异常定制错误的响应。

使用 @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);

 

   }

}

和第一种方式的区别在于, exceptionhandler 的定义和异常捕获可以扩展到全局。

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提供了一套函数式接口,可以用来实现类似mvc的效果。我们先接触两个常用的。

controller定义对request的处理逻辑的方式,主要有方面:

方法定义处理逻辑; 然后用@requestmapping注解定义好这个方法对什么样url进行响应。

在webflux的函数式开发模式中,我们用handlerfunction和routerfunction来实现上边这两点。

handlerfunction

handlerfunction 相当于controller中的具体处理方法,输入为请求,输出为装在mono中的响应:

?

1

mono<t> handle(serverrequest var1);

在webflux中,请求和响应不再是webmvc中的servletrequest和servletresponse,而是serverrequest和serverresponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和回压特性的支持,以及http消息体与响应式类型mono和flux的转换方法。

?

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 ,根据请求的参数返回当前时间。

routerfunction

routerfunction ,顾名思义,路由,相当于 @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)的情况下调用相同的请求地址,例如/time,它将抛出异常。

mono和flux apis内置了两个关键操作符,用于处理功能级别上的错误。

使用onerrorresume处理错误

还可以使用onerrorresume处理错误,fallback方法定义如下:

?

1

mono<t> onerrorresume(function<? super throwable, ? extends mono<? extends t>> fallback);

当出现错误时,我们使用fallback方法执行替代路径:

?

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();

     }

   }

}

在如上的实现中,每当 gettimebytype() 抛出异常时,将会执行我们定义的 fallback 方法。除此之外,我们还可以捕获、包装和重新抛出异常,例如作为自定义业务异常:

?

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处理错误

每当发生错误时,我们可以使用 onerrorreturn() 返回静态默认值:

?

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的函数式开发模式也可以进行全局异常处理。要做到这一点,我们只需要自定义全局错误响应属性,并且实现全局错误处理逻辑。

我们的处理程序抛出的异常将自动转换为http状态和json错误正文。要自定义这些,我们可以简单地扩展 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;

   }

   //...有省略

}

如上的实现中,我们对 serverexception 进行了特别处理,根据传入的 errorcode 对象构造对应的响应。

接下来,让我们实现全局错误处理程序。为此,spring提供了一个方便的 abstracterrorwebexceptionhandler 类,供我们在处理全局错误时进行扩展和实现:

?

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 {

 

  //构造函数

   @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));

   }

}

这里将全局错误处理程序的顺序设置为-2。这是为了让它比 @order(-1) 注册的 defaulterrorwebexceptionhandler 处理程序更高的优先级。

该errorattributes对象将是我们在网络异常处理程序的构造函数传递一个的精确副本。理想情况下,这应该是我们自定义的error attributes类。然后,我们清楚地表明我们想要将所有错误处理请求路由到rendererrorresponse()方法。最后,我们获取错误属性并将它们插入服务器响应主体中。

然后,它会生成一个json响应,其中包含错误,http状态和计算机客户端异常消息的详细信息。对于浏览器客户端,它有一个whitelabel错误处理程序,它以html格式呈现相同的数据。当然,这可以是定制的。

小结

本文首先讲了spring 5之前的springmvc异常处理机制,springmvc统一异常处理有 3 种方式:使用 @exceptionhandler 注解、实现 handlerexceptionresolver 接口、使用 @controlleradvice 注解;然后通过webflux的函数式接口构建web应用,讲解spring boot 2 webflux的函数级别和全局异常处理机制(对于spring webmvc风格,基于注解的方式编写响应式的web服务,仍然可以通过springmvc统一异常处理实现)。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:http://blueskykong测试数据/2018/12/18/webflux-error/

查看更多关于详解Spring Boot2 Webflux的全局异常处理的详细内容...

  阅读:32次