摘要: 原创出处 https://www.bysocket.com
本文内容
摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的。
前后端分离开发,一般提供 REST API,正常返回会有响应体,异常情况下会有对应的错误码响应。
挺多人咨询的,Spring Boot MVC 异常处理用切面 @RestControllerAdvice
注解去实现去全局异常处理。那 WebFlux 如何处理异常?如何实现统一错误码异常处理?
全局异常处理的好处:
下面介绍如何统一拦截异常,进行响应处理。
工程结构:
├── 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() 方法,就可以启动该应用。
具体实现类的关系图如下:
城市路由器代码如下:
@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) 方法,对应的 参是请求参数和处理函数,如果请求匹配,就调 对应的处理器函数。
城市服务器处理类,代码如下:
@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。
如图:
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)); } } 复制代码
代码解析如下:
到此基本结束。Spring Boot MVC 错误码如何实战,参考地址:https://www.bysocket.com/archives/1692
在 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 全局异常处理机制。实战中这块扩展需要考虑: