前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Gateway中异常处理

Spring Cloud Gateway中异常处理

作者头像
只喝牛奶的杀手
发布2019-08-26 17:49:40
1.6K0
发布2019-08-26 17:49:40
举报

最近我们的项目在考虑使用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自定义异常

两个类的代码如下(参考:http://cxytiandi.com/blog/detail/20548):

代码语言:javascript
复制
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class);
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

        /**
         * 获取异常属性
         */
        @Override
        protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
            int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
            Throwable error = super.getError(request);
            if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
                code = HttpStatus.NOT_FOUND.value();
            }
            return response(code, this.buildMessage(request, error));
        }

        /**
         * 指定响应处理方法为JSON处理的方法
         * @param errorAttributes
         */
        @Override
        protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
            return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
        }


        /**
         * 根据code获取对应的HttpStatus
         * @param errorAttributes
         */
        @Override
        protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
            int statusCode = (int) errorAttributes.get("code");
            return HttpStatus.valueOf(statusCode);
        }

        /**
         * 构建异常信息
         * @param request
         * @param ex
         * @return
         */
        private String buildMessage(ServerRequest request, Throwable ex) {
            StringBuilder message = new StringBuilder("Failed to handle request [");
            message.append(request.methodName());
            message.append(" ");
            message.append(request.uri());
            message.append("]");
            if (ex != null) {
                message.append(": ");
                message.append(ex.getMessage());
            }
            return message.toString();
        }

        /**
         * 构建返回的JSON数据格式
         * @param status      状态码
         * @param errorMessage  异常信息
         * @return
         */
        public static Map<String, Object> response(int status, String errorMessage) {
            Map<String, Object> map = new HashMap<>();
            map.put("code", status);
            map.put("message", errorMessage);
            map.put("data", null);
            logger.error(map.toString());
            return map;
        }

    }
代码语言:javascript
复制
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionHandlerConfiguration {

    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public ExceptionHandlerConfiguration(ServerProperties serverProperties,
                                         ResourceProperties resourceProperties,
                                         ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                         ServerCodecConfigurer serverCodecConfigurer,
                                         ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;

绿线代表Gateway转发异常

转发的异常,肯定是springboot单体中处理的,至于spring单体中的异常是怎么处理的呢?肯定是用@ControllerAdvice去做。

代码语言:javascript
复制
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ScanAppResponse exceptionHandler(HttpServletRequest request, Exception e) {
    String ip = RequestUtil.getIpAddress(request);
    logger.info("调用者IP:" + ip);
    String errorMessage = String.format("Url:[%s]%n{%s}", request.getRequestURL().toString(), e.getMessage());
    logger.error(errorMessage, e);
    return ScanAppResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}

到这里基本上可以了,大家不要试着去用Gateway去捕获后端异常,回到最初的起点,API 网关(API Gateway)主要负责服务请求路由、组合及协议转换,异常同样也是一样,Gateway只负责转发单体应用的异常,不要试图Gateway捕获后端服务异常,然后再输出给前端。感谢猿天地的一句惊醒梦中人!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 只喝牛奶的杀手 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档