Spring Boot 2 Webflux的全局异常处理
https://www.jianshu.com/p/6f631f3e00b9
本文首先将会回顾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接口,这里我们通过继承SimpleMappingExceptionResolver实现类(HandlerExceptionResolver实现,允许将异常类名称映射到视图名称,既可以是一组给定的handlers处理程序,也可以是DispatcherServlet中的所有handlers)定义全局异常:
@Component
public class CustomMvcExceptionHandler extends SimpleMappingExceptionResolver {
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统一异常处理实现)。
注:本文后半部分基本翻译自https://www.baeldung.com/spring-webflux-errors
Spring Boot 2 Webflux的全局异常处理的更多相关文章
- Spring Boot WebFlux-01——WebFlux 快速入门实践
第01课:WebFlux 快速入门实践 Spring Boot 2.0 spring.io 官网有句醒目的话是: BUILD ANYTHING WITH SPRING BOOT Spring Boot ...
- Spring Boot 系列教程6-全局异常处理
@ControllerAdvice源码 package org.springframework.web.bind.annotation; import java.lang.annotation.Ann ...
- spring boot+自定义 AOP 实现全局校验
最近公司重构项目,重构为最热的微服务框架 spring boot, 重构的时候遇到几个可以统一处理的问题,也是项目中经常遇到,列如:统一校验参数,统一捕获异常... 仅凭代码 去控制参数的校验,有时候 ...
- Spring Boot 统一返回结果及异常处理
在 Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理 基础上优化.调整 一.通用类 1.1 通用的返回对象 public class CommonReturn ...
- Spring Boot WebFlux-03——WebFlux 整合 MongoDB
第03课:WebFlux 整合 MongoDB 前言 上一课的内容讲解了用 Map 数据结构内存式存储了数据,这样数据就不会持久化,本文我们用 MongoDB 来实现 WebFlux 对数据源的操作. ...
- Spring Boot WebFlux-04——WebFlux 整合 Thymeleaf
第04课:WebFlux 整合 Thymeleaf 上一篇介绍的是用 MongoDB 来实现 WebFlux 对数据源的操作,那么有了数据需要渲染到前台给用户展示,这就是本文关心的 View 层,Vi ...
- Spring Boot WebFlux-06——WebFlux 整合 Redis
第06课:WebFlux 整合 Redis 前言 上一篇内容讲了如何整合 MongoDB,这里继续讲如何操作 Redis 这个数据源,那什么是 Reids? Redis 是一个高性能的 key-val ...
- Spring Boot WebFlux-09——WebFlux 集成测试及部署
第09课:WebFlux 集成测试及部署 前言 在日常工作中,免不了自测 UT,因为覆盖率不达标,是不允许提交测试,那怎么进行 WebFlux 项目的测试呢.@WebFluxTest 是 WebFlu ...
- Spring Boot WebFlux-10——WebFlux 实战图书管理系统
前言 本篇内容我们会实现如下图所示的城市管理系统,因为上面案例都用的是 City,所以这里直接使用城市作为对象,写一个简单的城市管理系统,如图所示: 结构 类似上面讲的工程搭建,新建一个工程编写此案例 ...
随机推荐
- wannafly25 E 01串
链接 wannafly25 E 01串 给出一个\(01\)串,有两种操作,操作一是将某一个位置的数字修改,操作二是询问某一个区间,将这个区间看做\(1\)个二进制数,可以随意加减\(2\)的幂次,问 ...
- bzoj4009 [HNOI2015]接水果 整体二分+扫描线+树状数组+dfs序
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4009 题解 考虑怎样的情况就会有一个链覆盖另一个链. 设被覆盖的链为 \(a - b\),覆盖 ...
- BZOJ 1233 干草堆 (单调队列优化DP)
$ BZOJ~1233~~ $ 干草堆: (题目特殊性质) $ solution: $ 很妙的一道题目,开始看了一眼觉得是个傻逼贪心,从后往前当前层能多短就多短,尽量节省花费.但是这是DP专题,怎么会 ...
- Git的配置与基本操作
Git是一个版本控制软件,它可以让我们能够拍摄处于可行状态的项目的快照,修改项目(如实现新功能)后,如果项目不能正常运行,可以恢复到前一个可行状态. 通过使用版本控制,我们可以无忧无虑的改进项目,不用 ...
- URL编码表
url编码是一种浏览器用来打包表单输入的格式. 定义 url编码是一种浏览器用来打包表单输入的格式.浏览器从表单中获取所有的name和其中的值 ,将它们以name/value参数编码(移去那些不能传送 ...
- python全栈开发,Day41(线程概念,线程的特点,进程和线程的关系,线程和python理论知识,线程的创建)
昨日内容回顾 队列 队列:先进先出.数据进程安全 队列实现方式:管道+锁 生产者消费者模型:解决数据供需不平衡 管道 双向通信,数据进程不安全 EOFError: 管道是由操作系统进行引用计数的 必须 ...
- Web截屏插件
官方网站:http://www.ncmem.com 官方博客:http://www.cnblogs.com/xproer 产品首页:http://www.ncmem.com/webplug/scppr ...
- UE4开发PSVR游戏的常见问题
Failed to connect to file server at xxx.xxx.xxx.xxx. RETRYING in 5s解决办法:PS4 Devkit 中 Settings->De ...
- scau 17967 大师姐唱K的固有结界
17967 大师姐唱K的固有结界 该题有题解 时间限制:1000MS 内存限制:65535K 提交次数:41 通过次数:8 收入:107 题型: 编程题 语言: G++;GCC;VC Descr ...
- Windows7系统C盘空间不足
C盘要满了,用WizTree发现:;两个大文件, ①睡眠有关的,用命令提示符(管理员身份运行), 命令窗口中输入 powercfg -h off,即可关闭休眠功能,同时 Hiberfil.sys 文件 ...