详解Spring Boot2 Webflux的全局异常处理
本文首先将会回顾Spring5之前的SpringMVC异常处理机制,然后主要讲解SpringBoot2Webflux的全局异常处理机制。
SpringMVC的异常处理
Spring统一异常处理有3种方式,分别为:
- 使用@ExceptionHandler注解
- 实现HandlerExceptionResolver接口
- 使用@controlleradvice注解
使用@ExceptionHandler注解
用于局部方法捕获,与抛出异常的方法处于同一个Controller类:
@Controller publicclassBuzController{ @ExceptionHandler({NullPointerException.class}) publicStringexception(NullPointerExceptione){ System.out.println(e.getMessage()); e.printStackTrace(); return"nullpointerexception"; } @RequestMapping("test") publicvoidtest(){ thrownewNullPointerException("出错了!"); } }
如上的代码实现,针对BuzController抛出的NullPointerException异常,将会捕获局部异常,返回指定的内容。
实现HandlerExceptionResolver接口
通过实现HandlerExceptionResolver接口,定义全局异常:
@Component publicclassCustomMvcExceptionHandlerimplementsHandlerExceptionResolver{ privateObjectMapperobjectMapper; publicCustomMvcExceptionHandler(){ objectMapper=newObjectMapper(); } @Override publicModelAndViewresolveException(HttpServletRequestrequest,HttpServletResponseresponse, Objecto,Exceptionex){ response.setStatus(200); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control","no-cache,must-revalidate"); Mapmap=newHashMap<>(); if(exinstanceofNullPointerException){ map.put("code",ResponseCode.NP_EXCEPTION); }elseif(exinstanceofIndexOutOfBoundsException){ 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(Exceptione){ e.printStackTrace(); } returnnewModelAndView(); } }
如上为示例的使用方式,我们可以根据各种异常定制错误的响应。
使用@controlleradvice注解
@ControllerAdvice publicclassExceptionController{ @ExceptionHandler(RuntimeException.class) publicModelAndViewhandlerRuntimeException(RuntimeExceptionex){ if(exinstanceofMaxUploadSizeExceededException){ returnnewModelAndView("error").addObject("msg","文件太大!"); } returnnewModelAndView("error").addObject("msg","未知错误:"+ex); } @ExceptionHandler(Exception.class) publicModelAndViewhandlerMaxUploadSizeExceededException(Exceptionex){ if(ex!=null){ returnnewModelAndView("error").addObject("msg",ex); } returnnewModelAndView("error").addObject("msg","未知错误:"+ex); } }
和第一种方式的区别在于,ExceptionHandler的定义和异常捕获可以扩展到全局。
Spring5Webflux的异常处理
webflux支持mvc的注解,是一个非常便利的功能,相比较于RouteFunction,自动扫描注册比较省事。异常处理可以沿用ExceptionHandler。如下的全局异常处理对于RestController依然生效。
@RestControllerAdvice publicclassCustomExceptionHandler{ privatefinalLoglogger=LogFactory.getLog(getClass()); @ExceptionHandler(Exception.class) @ResponseStatus(code=HttpStatus.OK) publicErrorCodehandleCustomException(Exceptione){ logger.error(e.getMessage()); returnnewErrorCode("e","error"); } }
WebFlux示例
WebFlux提供了一套函数式接口,可以用来实现类似MVC的效果。我们先接触两个常用的。
Controller定义对Request的处理逻辑的方式,主要有方面:
- 方法定义处理逻辑;
- 然后用@RequestMapping注解定义好这个方法对什么样url进行响应。
在WebFlux的函数式开发模式中,我们用HandlerFunction和RouterFunction来实现上边这两点。
HandlerFunction
HandlerFunction相当于Controller中的具体处理方法,输入为请求,输出为装在Mono中的响应:
Monohandle(ServerRequestvar1);
在WebFlux中,请求和响应不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和回压特性的支持,以及Http消息体与响应式类型Mono和Flux的转换方法。
@Component publicclassTimeHandler{ publicMonogetTime(ServerRequestserverRequest){ StringtimeType=serverRequest.queryParam("type").get(); //return... } }
如上定义了一个TimeHandler,根据请求的参数返回当前时间。
RouterFunction
RouterFunction,顾名思义,路由,相当于@RequestMapping,用来判断什么样的url映射到那个具体的HandlerFunction。输入为请求,输出为Mono中的Handlerfunction:
Mono>route(ServerRequestvar1);
针对我们要对外提供的功能,我们定义一个Route。
@Configuration publicclassRouterConfig{ privatefinalTimeHandlertimeHandler; @Autowired publicRouterConfig(TimeHandlertimeHandler){ this.timeHandler=timeHandler; } @Bean publicRouterFunctiontimerRouter(){ returnroute(GET("/time"),req->timeHandler.getTime(req)); } }
可以看到访问/time的GET请求,将会由TimeHandler::getTime处理。
功能级别处理异常
如果我们在没有指定时间类型(type)的情况下调用相同的请求地址,例如/time,它将抛出异常。
Mono和FluxAPIs内置了两个关键操作符,用于处理功能级别上的错误。
使用onErrorResume处理错误
还可以使用onErrorResume处理错误,fallback方法定义如下:
MonoonErrorResume(Function>fallback);
当出现错误时,我们使用fallback方法执行替代路径:
@Component publicclassTimeHandler{ publicMonogetTime(ServerRequestserverRequest){ StringtimeType=serverRequest.queryParam("time").orElse("Now"); returngetTimeByType(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))); } privateMono getTimeByType(StringtimeType){ Stringtype=Optional.ofNullable(timeType).orElse( "Now" ); switch(type){ case"Now": returnMono.just("Nowis"+newSimpleDateFormat("HH:mm:ss").format(newDate())); case"Today": returnMono.just("Todayis"+newSimpleDateFormat("yyyy-MM-dd").format(newDate())); default: returnMono.empty(); } } }
在如上的实现中,每当getTimeByType()抛出异常时,将会执行我们定义的fallback方法。除此之外,我们还可以捕获、包装和重新抛出异常,例如作为自定义业务异常:
publicMonogetTime(ServerRequestserverRequest){ StringtimeType=serverRequest.queryParam("time").orElse("Now"); returnServerResponse.ok() .body(getTimeByType(timeType) .onErrorResume(e->Mono.error(newServerException(newErrorCode(HttpStatus.BAD_REQUEST.value(), "timeTypeisrequired",e.getMessage())))),String.class); }
使用onErrorReturn处理错误
每当发生错误时,我们可以使用onErrorReturn()返回静态默认值:
publicMonogetDate(ServerRequestserverRequest){ StringtimeType=serverRequest.queryParam("time").get(); returngetTimeByType(timeType) .onErrorReturn("Todayis"+newSimpleDateFormat("yyyy-MM-dd").format(newDate())) .flatMap(s->ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN).syncBody(s)); }
全局异常处理
如上的配置是在方法的级别处理异常,如同对注解的Controller全局异常处理一样,WebFlux的函数式开发模式也可以进行全局异常处理。要做到这一点,我们只需要自定义全局错误响应属性,并且实现全局错误处理逻辑。
我们的处理程序抛出的异常将自动转换为HTTP状态和JSON错误正文。要自定义这些,我们可以简单地扩展DefaultErrorAttributes类并覆盖其getErrorAttributes()方法:
@Component publicclassGlobalErrorAttributesextendsDefaultErrorAttributes{ publicGlobalErrorAttributes(){ super(false); } @Override publicMapgetErrorAttributes(ServerRequestrequest,booleanincludeStackTrace){ returnassembleError(request); } privateMap assembleError(ServerRequestrequest){ Map errorAttributes=newLinkedHashMap<>(); Throwableerror=getError(request); if(errorinstanceofServerException){ errorAttributes.put("code",((ServerException)error).getCode().getCode()); errorAttributes.put("data",error.getMessage()); }else{ errorAttributes.put("code",HttpStatus.INTERNAL_SERVER_ERROR); errorAttributes.put("data","INTERNALSERVERERROR"); } returnerrorAttributes; } //...有省略 }
如上的实现中,我们对ServerException进行了特别处理,根据传入的ErrorCode对象构造对应的响应。
接下来,让我们实现全局错误处理程序。为此,Spring提供了一个方便的AbstractErrorWebExceptionHandler类,供我们在处理全局错误时进行扩展和实现:
@Component @Order(-2) publicclassGlobalErrorWebExceptionHandlerextendsAbstractErrorWebExceptionHandler{ //构造函数 @Override protectedRouterFunctiongetRoutingFunction(finalErrorAttributeserrorAttributes){ returnRouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse); } privateMono renderErrorResponse(finalServerRequestrequest){ finalMap errorPropertiesMap=getErrorAttributes(request,true); returnServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(errorPropertiesMap)); } }
这里将全局错误处理程序的顺序设置为-2。这是为了让它比@Order(-1)注册的DefaultErrorWebExceptionHandler处理程序更高的优先级。
该errorAttributes对象将是我们在网络异常处理程序的构造函数传递一个的精确副本。理想情况下,这应该是我们自定义的ErrorAttributes类。然后,我们清楚地表明我们想要将所有错误处理请求路由到renderErrorResponse()方法。最后,我们获取错误属性并将它们插入服务器响应主体中。
然后,它会生成一个JSON响应,其中包含错误,HTTP状态和计算机客户端异常消息的详细信息。对于浏览器客户端,它有一个whitelabel错误处理程序,它以HTML格式呈现相同的数据。当然,这可以是定制的。
小结
本文首先讲了Spring5之前的SpringMVC异常处理机制,SpringMVC统一异常处理有3种方式:使用@ExceptionHandler注解、实现HandlerExceptionResolver接口、使用@controlleradvice注解;然后通过WebFlux的函数式接口构建Web应用,讲解SpringBoot2Webflux的函数级别和全局异常处理机制(对于SpringWebMVC风格,基于注解的方式编写响应式的Web服务,仍然可以通过SpringMVC统一异常处理实现)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。