SpringCloudGateway中异常处理
最近我们的项⽬在考虑使⽤Gateway,考虑使⽤Spring Cloud Gateway,发现⽹关的异常处理和spring boot 单体应⽤异常处理还是有很⼤区别的。让我们来回顾⼀下异常。
关于异常是拿来⼲什么的,很多⼈⽼程序员认为就是拿来我们Debug的时候排错的,当然这⼀点确实是异常机制⾮常⼤的⼀个好处,但异常机制包含着更多的意义。
关注业务实现。异常机制使得业务代码与异常处理代码可以分开,你可以将⼀些你调⽤数据库操作的代码写在⼀个⽅法⾥⽽只需要在⽅法上加上throw DB相关的异常。⾄于如何处理它,你可以在调⽤该⽅法的时候处理或者甚⾄选择不处理,⽽不是直接在该⽅法内部添加上if判断如果数据库操作错误该如何办,这样业务代码会⾮常混乱。
统⼀异常处理。与上⼀点有所联系。我当前所在项⽬的实践是,⾃定义业务类异常,在Controller或Service中抛出,让后使⽤Spring提供的异常接⼝统⼀处理我们⾃⼰在内部抛出的异常。这样⼀个异常处理架构就⾮常明了。
程序的健壮性。如果没有异常机制,那么来了个对空对象的某⽅法调⽤怎么办呢?直接让程序挂掉?这令⼈⽆法接受,当然,我们⾃⼰平时写的⼀些⼩的东西确实是这样,没有处理它,让后程序挂了。但在web
框架中,可以利⽤异常处理机制捕获该异常并将错误信息传递给我们然后继续处理下个请求。所以异常对于健壮性是⾮常有帮助的。
异常处理(⼜称为错误处理)功能提供了处理程序运⾏时出现的任何意外或异常情况的⽅法。异常处理使⽤ try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。异常根据意义成三种:业务、系统、代码异常,不同的异常采⽤不同的处理⽅式。具体的什么样的异常怎么处理就不说了。
红线和绿线代表两条异常路径
1,红线代表:请求到Gateway发⽣异常,可能由于后端app在启动或者是没启动
2,绿线代表:请求到Gateway转发到后端app,后端app发⽣异常,然后Gateway转发后端异常到前端
红线肯定是⾛Gateway⾃定义异常:
1 @Configuration
2 @EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
3public class ExceptionHandlerConfiguration {
4
5private final ServerProperties serverProperties;
6
7private final ApplicationContext applicationContext;
8
9private final ResourceProperties resourceProperties;
10
11private final List<ViewResolver> viewResolvers;
12
13private final ServerCodecConfigurer serverCodecConfigurer;
14
15public ExceptionHandlerConfiguration(ServerProperties serverProperties,
16                                          ResourceProperties resourceProperties,
17                                          ObjectProvider<List<ViewResolver>> viewResolversProvider,
18                                          ServerCodecConfigurer serverCodecConfigurer,
19                                          ApplicationContext applicationContext) {
20this.serverProperties = serverProperties;
21this.applicationContext = applicationContext;
23this.viewResolvers = IfAvailable(Collections::emptyList);
24this.serverCodecConfigurer = serverCodecConfigurer;
25    }
26
27    @Bean
28    @Order(Ordered.HIGHEST_PRECEDENCE)
29public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
30        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
31                errorAttributes,
Error(),
34this.applicationContext);
35        exceptionHandler.setViewResolvers(this.viewResolvers);
36        exceptionHandler.setMessageWriters(Writers());
37        exceptionHandler.setMessageReaders(Readers());
38return exceptionHandler;
39    }
1public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
2
3private static Logger logger = Logger(JsonExceptionHandler.class);
4
5public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
6                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
7super(errorAttributes, resourceProperties, errorProperties, applicationContext);
8    }
9
10/**
11        * 获取异常属性
12*/
13        @Override
14protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
15int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
16            Throwable error = Error(request);
17if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
18                code = HttpStatus.NOT_FOUND.value();
19            }
20return response(code, this.buildMessage(request, error));
21        }
22
23/**
24        * 指定响应处理⽅法为JSON处理的⽅法
25        * @param errorAttributes
26*/
27        @Override
28protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
ute(RequestPredicates.all(), this::renderErrorResponse);
30        }
31
32
33/**
34        * 根据code获取对应的HttpStatus
35        * @param errorAttributes
springcloud和springboot36*/
37        @Override
38protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
39int statusCode = (int) ("code");
40return HttpStatus.valueOf(statusCode);
41        }
42
43/**
44        * 构建异常信息
45        * @param request
46        * @param ex
47        * @return
48*/
49private String buildMessage(ServerRequest request, Throwable ex) {
50            StringBuilder message = new StringBuilder("Failed to handle request [");
51            message.hodName());
52            message.append(" ");
53            message.append(request.uri());
54            message.append("]");
55if (ex != null) {
56                message.append(": ");
57                message.Message());
58            }
String();
60        }
61
62/**
63        * 构建返回的JSON数据格式
64        * @param status        状态码
65        * @param errorMessage  异常信息
66        * @return
67*/
68public static Map<String, Object> response(int status, String errorMessage) {
69            Map<String, Object> map = new HashMap<>();
70            map.put("code", status);
71            map.put("message", errorMessage);
72            map.put("data", null);
73            (String());
74return map;
75        }
76    }
绿线代表Gateway转发异常
转发的异常,肯定是springboot单体中处理的,⾄于spring单体中的异常是怎么处理的呢?肯定是⽤@ControllerAdvice去做。
1    @ExceptionHandler(value = Exception.class)
2    @ResponseBody
3public AppResponse exceptionHandler(HttpServletRequest request, Exception e) {
4        String ip = IpAddress(request);
5        logger.info("调⽤者IP:" + ip);
6        String errorMessage = String.format("Url:[%s]%n{%s}", RequestURL().toString(), e.getMessage());
7        (errorMessage, e);
(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
9    }
到这⾥基本上可以了,⼤家不要试着去⽤Gateway去捕获后端异常,回到最初的起点,API ⽹关(API Gateway)主要负责服务请求路由、组合及协议转换,异常同样也是⼀样,Gateway只负责转发单体应⽤的异常,不要试图Gateway捕获后端服务异常,然后再输出给前端。感谢猿天地的⼀句惊醒梦中⼈!