Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error Handling
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢!
本文内容
- 为什么要全局异常处理?
- WebFlux REST 全局异常处理实战
- 小结
摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的。
一、为什么要全局异常处理?
前后端分离开发,一般提供 REST API,正常返回会有响应体,异常情况下会有对应的错误码响应。
挺多人咨询的,Spring Boot MVC 异常处理用切面 @RestControllerAdvice
注解去实现去全局异常处理。那 WebFlux 如何处理异常?如何实现统一错误码异常处理?
全局异常处理的好处:
- 异常错误码等统一维护
- 避免一些重复代码
二、WebFlux REST 全局异常处理实战
下面介绍如何统一拦截异常,进行响应处理。
2.1 工程信息
- 运行环境:JDK 7 或 8,Maven 3.0+
- 技术栈:SpringBoot 2.1.3
- 代码地址:https://github.com/JeffLi1993/springboot-learning-example
- 模块工程名: 2-x-spring-boot-webflux-handling-errors
工程结构:
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── Application.java
│ ├── error
│ │ ├── GlobalErrorAttributes.java
│ │ ├── GlobalErrorWebExceptionHandler.java
│ │ └── GlobalException.java
│ ├── handler
│ │ └── CityHandler.java
│ └── router
│ └── CityRouter.java
└── resources
└── application.properties
application.properties 无须配置,默认即可
Application Spring Boot 应用启动类,是可以用来启动 Spring Boot 应用。其包含了 @SpringBootApplication 注解和 SpringApplication 类,并调用 SpringApplication 类的 run() 方法,就可以启动该应用。
具体实现类的关系图如下:
2.2 CityRouter 路由器类
城市路由器代码如下:
@Configuration
public class CityRouter {
@Bean
public RouterFunction<ServerResponse> routeCity(CityHandler cityHandler) {
return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), cityHandler::helloCity);
}
}
RouterFunctions 对请求路由处理类,即将请求路由到处理器,这将一个 GET 请求 /hello 路由到处理器 cityHandler 的 helloCity 方法上。跟 Spring MVC 模式下的 HandleMapping 类似。
RouterFunctions.route(RequestPredicate, HandlerFunction) 方法,对应的 参是请求参数和处理函数,如果请求匹配,就调 对应的处理器函数。
2.3 CityHandler 服务处理类
城市服务器处理类,代码如下:
@Component
public class CityHandler {
public Mono<ServerResponse> helloCity(ServerRequest request) {
return ServerResponse.ok().body(sayHelloCity(request), String.class);
}
private Mono<String> sayHelloCity(ServerRequest request) {
Optional<String> cityParamOptional = request.queryParam("city");
if (!cityParamOptional.isPresent()) {
throw new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "request param city is ERROR");
}
return Mono.just("Hello," + cityParamOptional.get());
}
}
Mono:实现发布者,并返回 0 或 1 个元素,即单对象。Mono 是响应流 Publisher 具有基础 rx 操作符。可以成功发布元素或者错误。用 Mono 作为返回对象,是因为返回包含了一个 ServerResponse 对象,而不是多个元素。
ServerResponse 是对响应的封装,可以设置响应状态,响应头,响应正文。比如 ok 代表的是 200 响应码、MediaType 枚举是代表这文本内容类型、返回的是 String 的对象。
ServerRequest 是对请求的封装。从请求中拿出 city 的值,如果没有的话则抛出对应的异常。GlobalException 是封装的全局异常。
Mono.justOrEmpty():从一个 Optional 对象或 null 对象中创建 Mono。
2.4 GlobalError 处理类
如图:
GlobalException 全局异常类,代码如下:
public class GlobalException extends ResponseStatusException {
public GlobalException(HttpStatus status, String message) {
super(status, message);
}
public GlobalException(HttpStatus status, String message, Throwable e) {
super(status, message, e);
}
}
GlobalErrorAttributes 全局异常属性值类,代码如下:
@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(request, includeStackTrace);
if (getError(request) instanceof GlobalException) {
GlobalException ex = (GlobalException) getError(request);
map.put("exception", ex.getClass().getSimpleName());
map.put("message", ex.getMessage());
map.put("status", ex.getStatus().value());
map.put("error", ex.getStatus().getReasonPhrase());
return map;
}
map.put("exception", "SystemException");
map.put("message", "System Error , Check logs!");
map.put("status", "500");
map.put("error", " System Error ");
return map;
}
}
重写了父类 DefaultErrorAttributes 默认错误属性类的 getErrorAttributes 获取错误属性方法,从服务请求封装 ServerRequest 中获取对应的异常。
然后判断是否是 GlobalException,如果是 CityHandler 服务处理类抛出的 GlobalException,则返回对应的异常的信息。
GlobalErrorWebExceptionHandler 全局异常处理类,代码如下:
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes g, ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(g, new ResourceProperties(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
@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, false);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorPropertiesMap));
}
}
代码解析如下:
- AbstractErrorWebExceptionHandler 抽象类是用来处理全局错误时进行扩展和实现
- @Order 注解标记 AspectJ 的切面排序,值越小拥有越高的优先级,这里设置优先级偏高。
- 构造函数将 GlobalErrorAttributes 全局异常属性值类设置到 AbstractErrorWebExceptionHandler 抽象类的局部变量中。
- 重写 getRoutingFunction 方法,设置对应的 RequestPredicates 和 Mono 服务响应对象
- 将 GlobalErrorAttributes 的全局异常属性值 map,设置到新的 ServerResponse 即可。
到此基本结束。Spring Boot MVC 错误码如何实战,参考地址:https://www.bysocket.com/archives/1692
2.5 运行验证
在 IDEA 中执行 Application
类启动,任意正常模式或者 Debug 模式。然后打开浏览器访问:
http://localhost:8080/hello
异常界面如下:
可见,这是在 CityHandler 城市服务处理类逻辑中抛出的全局异常信息。那么正常情况会是如何?
改下 URL ,访问如下:
http://localhost:8080/hello?city=WenLing
正常界面如下:
三、小结
在 Spring 框架中没有代表错误响应的类,只是返回响应对象,一个 Map。如果需要定义业务的错误码返回体,参考错误码如何实战,参考地址:https://www.bysocket.com/archives/1692。
本文重点还是有别于 Spring Boot 传统 MVC 模式统一异常处理,实战了 WebFlux 全局异常处理机制。实战中这块扩展需要考虑:
- 异常分层,从基类中扩展出来
- 错误码设计分层,易扩展,比如在错误码中新增调用量字段…
代码示例
本文示例读者可以通过查看下面仓库的中的模块工程名: 2-x-spring-boot-webflux-handling-errors:
- Github:https://github.com/JeffLi1993/springboot-learning-example
- Gitee:https://gitee.com/jeff1993/springboot-learning-example
如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持!
参考资料
- WebFlux REST API 全局异常处理:https://www.bysocket.com/archives/2100
- https://dzone.com/articles/exception-handling-in-spring-boot-webflux-reactive
以下专题教程也许您会有兴趣
- 《Spring Boot 2.x 系列教程》 https://www.bysocket.com/springboot
- 《Java 核心系列教程》 https://www.bysocket.com/archives/2100
(关注微信公众号,领取 Java 精选干货学习资料)
Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error Handling的更多相关文章
- SpringBoot系列教程web篇之全局异常处理
当我们的后端应用出现异常时,通常会将异常状况包装之后再返回给调用方或者前端,在实际的项目中,不可能对每一个地方都做好异常处理,再优雅的代码也可能抛出异常,那么在 Spring 项目中,可以怎样优雅的处 ...
- Spring Boot 2.x 系列教程:WebFlux 系列教程大纲(一)
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! WebFlux 系列教程大纲 一.背景 大家都知道,Sprin ...
- SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver
关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理: 本篇博文则带来另外一种并不常见的使用方式,通过实 ...
- Spring Boot 2.x基础教程:找回启动日志中的请求路径列表
如果您看过之前的Spring Boot 1.x教程,或者自己原本就对Spring Boot有一些经验,或者对Spring MVC很熟悉.那么对于Spring构建的Web应用在启动的时候,都会输出当前应 ...
- Spring Boot 2.x基础教程:使用MyBatis的XML配置方式
上一篇我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问.但是上一篇中使用了注解方式来实现,而对于很多MyBatis老用户还是习惯于XML的开发方式, ...
- Spring Boot 2.x基础教程:EhCache缓存的使用
上一篇我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问.可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢? 在Spring Boot中通过@EnableCachi ...
- Spring Boot 2.x基础教程:使用EhCache缓存集群
上一篇我们介绍了在Spring Boot中整合EhCache的方法.既然用了ehcache,我们自然要说说它的一些高级功能,不然我们用默认的ConcurrentHashMap就好了.本篇不具体介绍Eh ...
- Spring Boot 2.x基础教程:使用集中式缓存Redis
之前我们介绍了两种进程内缓存的用法,包括Spring Boot默认使用的ConcurrentMap缓存以及缓存框架EhCache.虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进 ...
- Spring Boot 2.x基础教程:使用JTA实现多数据源的事务管理
在一个Spring Boot项目中,连接多个数据源还是比较常见的.之前也介绍了如何在几种常用框架的场景下配置多数据源,具体可见: Spring Boot 2.x基础教程:JdbcTemplate的多数 ...
随机推荐
- BZOJ_3687_简单题_bitset
BZOJ_3687_简单题_bitset Description 小呆开始研究集合论了,他提出了关于一个数集四个问题: 1.子集的异或和的算术和. 2.子集的异或和的异或和. 3.子集的算术和的算术和 ...
- json与javabean之间的转化
接着上一个http://www.cnblogs.com/ya-qiang/p/9009134.html随笔,继续介绍json与java对象之间的转化 一.java普通对象和json字符串的互转 jav ...
- Java 11 新功能来了!
关键时刻,第一时间送达! 目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出 ...
- js面试题1
1.介绍js的基本数据类型 Undefined.Null.Boolean.Number.String 2.js有哪些内置对象? 数据封装类对象:Object.Array.Boolean.Number ...
- OpenLayer实现路径运动
近期由于业务的需求,让我这从未想过要碰Web Gis的业余前端开发者,走了Web Gis的开发道路.功能需求很简单,但却也是让自己难为了好几天.如,应该选择那个Gis框架,Gis框架的兼容性如何,直接 ...
- 【推荐】 HyperLedger Fabric环境搭建、测试及注意事项 [详尽指导] [亲测有效]
系统:Ubuntu16.04 LTS 一.环境准备 1.1 Ubuntu下安装 crul sudo apt install curl curl是利用URL语法在命令行方式下工作的开源文件传输工具.它被 ...
- 我为什么放弃MySQL?最终选择了MongoDB
最近有个项目的功能模块,为了处理方便,需要操作集合类型的数据以及其他原因.考虑再三最终决定放弃使用MySQL,而选择MongoDB. 两个数据库,大家应该都不陌生.他们最大的区别就是MySQL为关系型 ...
- Unity C#笔记 协程
什么是协程 协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行. 可能看了这段文字介绍还是有点模糊,其实可以用多线程来比较. 多线程 多线程,顾名思义,多条同时执行的线程. 最初 ...
- 设计模式之Singleton模式和Strategy模式是什么
Singleton模式 单例模式,也交单子模式,有时候系统只需要拥有一个全局对象. 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建.这个类提供了一种访问其唯一的对象的方 ...
- Android开发利器之stetho
文章同步自javaexception Stetho是什么? github上地址https://github.com/facebook/stetho stetho是facebook出品的一款开发调试工具 ...