评论

收藏

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

编程语言 编程语言 发布于:2021-10-06 15:20 | 阅读数:549 | 评论:0

这篇文章主要介绍了详解Spring Boot2 Webflux的全局异常处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
本文首先将会回顾spring 5之前的springmvc异常处理机制,然后主要讲解spring boot 2 webflux的全局异常处理机制。
springmvc的异常处理
spring 统一异常处理有 3 种方式,分别为:

  • 使用 @exceptionhandler 注解
  • 实现 handlerexceptionresolver 接口
  • 使用 @controlleradvice 注解
使用 @exceptionhandler 注解
用于局部方法捕获,与抛出异常的方法处于同一个controller类:
@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 接口,定义全局异常:
@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 注解
@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依然生效。
@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中的响应:
mono<t> handle(serverrequest var1);
在webflux中,请求和响应不再是webmvc中的servletrequest和servletresponse,而是serverrequest和serverresponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和回压特性的支持,以及http消息体与响应式类型mono和flux的转换方法。
@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 :
mono<handlerfunction<t>> route(serverrequest var1);
针对我们要对外提供的功能,我们定义一个route。
@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方法定义如下:
mono<t> onerrorresume(function<? super throwable, ? extends mono<? extends t>> fallback);
当出现错误时,我们使用fallback方法执行替代路径:
@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 方法。除此之外,我们还可以捕获、包装和重新抛出异常,例如作为自定义业务异常:
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() 返回静态默认值:
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() 方法:
@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 类,供我们在处理全局错误时进行扩展和实现:
@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统一异常处理实现)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家
原文链接:http://blueskykong.com/2018/12/18/webflux-error/

关注下面的标签,发现更多相似文章