• springcloud Zuul中异常处理细节


    Spring Cloud Zuul对异常的处理整体来说还是比较方便的,流程也比较清晰,只是由于Spring Cloud发展较快,各个版本之间有差异,导致有的小伙伴在寻找这方面的资料的时候经常云里雾里,本文将以Dalston.SR3版本为例,来说明Spring Cloud Zuul中的异常处理问题。

    首先我们来看一张官方给出的Zuul请求的生命周期图,如下:

    关于这张图我说如下几点:

    1. 正常情况下所有的请求都是按照pre、route、post的顺序来执行,然后由post返回response

    2. 在pre阶段,如果有自定义的过滤器则执行自定义的过滤器

    3. pre、routing、post的任意一个阶段如果抛异常了,则执行error过滤器,然后再执行post给出响应

    这是这张图给我们的信息,我们再来看看源码com.netflix.zuul.http.ZuulServlet类中的service方法,这是整个调用过程的核心,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    try {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
        // Marks this request as having passed through the "Zuul engine", as opposed to servlets
        // explicitly bound in web.xml, for which requests will not have the same data attached
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
        try {
            preRoute();
        catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            route();
        catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            postRoute();
        catch (ZuulException e) {
            error(e);
            return;
        }
    catch (Throwable e) {
        error(new ZuulException(e, 500"UNHANDLED_EXCEPTION_" + e.getClass().getName()));
    finally {
        RequestContext.getCurrentContext().unset();
    }

    我们看到这里有一个大的try…catch,大的try…catch里边有三个小的try…catch,小的try…catch只负责捕获ZuulException异常,其他的异常交给大的try…catch来捕获。pre和route执行出错之后都会先执行error再执行post,而post执行出错之后就只执行error而不会再执行post。

    ZuulFilter最终会在com.netflix.zuul.FilterProcessor的processZuulFilter方法中被调用,在该方法中会判断runFilter是否执行成功,如果执行失败,则将异常信息提取出来,然后抛出异常,抛出的异常如果是ZuulException的实例,则抛出一个ZuulException类型的异常,如果不是ZuulException的实例,则抛出一个状态码为500的ZuulException类型的异常,所以无论如何,我们最终看到的都是ZuulException类型的异常,下面我贴出processZuulFilter方法的一部分核心代码,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        try {
            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;
            switch (s) {
                case FAILED:
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    break;
                default:
                    break;
            }
            if (t != nullthrow t;
            usageNotifier.notify(filter, s);
            return o;
        catch (Throwable e) {
            usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (e instanceof ZuulException) {
                throw (ZuulException) e;
            else {
                ZuulException ex = new ZuulException(e, "Filter threw Exception"500
                filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }

    在Zuul中,所有的错误问题的最终都是被SendErrorFilter类来处理,该类在早期的版本是一个post类型的filter,post类型的filter有一个缺陷就是不能处理post中抛出的异常,需要我们手动去完善,而我目前使用的这个版本(Dalston.SR3)已经修复了这个问题,SendErrorFilter现在是一个error类型的filter,而且只要RequestContext中有异常就会进入到SendErrorFilter中,错误信息也都从exception对象中提取出来,核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // only forward to errorPath if it hasn't been forwarded to already
        return ctx.getThrowable() != null
                && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }
    @Override
    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            ZuulException exception = findZuulException(ctx.getThrowable());
            HttpServletRequest request = ctx.getRequest();
            request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
            log.warn("Error during filtering", exception);
            request.setAttribute("javax.servlet.error.exception", exception);
            if (StringUtils.hasText(exception.errorCause)) {
                request.setAttribute("javax.servlet.error.message", exception.errorCause);
            }
            RequestDispatcher dispatcher = request.getRequestDispatcher(
                    this.errorPath);
            if (dispatcher != null) {
                ctx.set(SEND_ERROR_FILTER_RAN, true);
                if (!ctx.getResponse().isCommitted()) {
                    dispatcher.forward(request, ctx.getResponse());
                }
            }
        }
        catch (Exception ex) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }

    如果我们想要自定义异常信息也很方便,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Component
    public class MyErrorAttribute extends DefaultErrorAttributes {
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
            Map<String, Object> result = super.getErrorAttributes(requestAttributes, includeStackTrace);
            result.put("status"222);
            result.put("error""error");
            result.put("exception""exception");
            result.put("message""message");
            return result;
        }
    }

    好了,关于Spring Cloud Zuul中异常处理我们就说这么多,笔者之前有一篇文章介绍了Spring Boot中的异常处理,想深入了解异常处理的小伙伴可以查看一下那篇文章,OK,有问题欢迎留言讨论。

  • 相关阅读:
    又见JavaWeb的中文乱码
    Java 中文字符串编码之GBK转UTF-8
    关于Java项目打包
    FreeSWITCH无法读取wav文件
    CentOS 7 最小化安装的网络配置
    虚拟机无法使用网卡桥接模式
    阿里云建站流程
    Spring MVC无法获取ajax POST的参数和值
    Mysql ERROR 1064 (42000)
    eclipse创建java类时自动添加注释
  • 原文地址:https://www.cnblogs.com/xyhero/p/f8b6a987325c004f8e0ec3b7d9e2c2fe.html
Copyright © 2020-2023  润新知