在之前的文章《Spring Cloud源码分析(四)Zuul:核心过滤器》中,我们详细介绍了SpringCloudZuul中实现的一些核心过滤器,以及这些过滤器在请求生命中的不同角色是如何实现的在循环中。我们会发现,错误阶段的过滤器并没有在这些核心过滤器中实现。那么这些过滤器可以用来做什么呢?接下来,本文将介绍如何使用错误过滤器来实现统一的异常处理。filter中抛出异常的问题首先我们可以看一下当filter中默认抛出异常时SpringCloudZuul会发生什么。我们创建一个pre类型的过滤器,并在过滤器的run方法的实现中抛出异常。例如,在下面的实现中,run方法中调用的doSomething方法会抛出RuntimeException。publicclassThrowExceptionFilterextendsZuulFilter{privatestaticLoggerlog=LoggerFactory.getLogger(ThrowExceptionFilter.class);@OverridepublicStringfilterType(){return"pre";}@OverridepublicintfilterOrder(){return0;}@OverridepublicbooleanshouldFilter(){@OverridepublicbooleanshouldFilter(){inoverfuleplic}("Thisisaprefilter,itwill"throwRuntimeException);doSomething();returnnull;}privatevoiddoSomething(){thrownewRuntimeException("Existssomeerrors...");}}运行网关程序,接入一个路由请求,此时我们会发现:API网关服务中的日志信息在ThrowExceptionFilter的过滤逻辑中,控制台输出了异常信息,但没有输出异常信息,同时发起的请求也得不到任何响应结果。为什么会这样?我们如何处理过滤器中的异常?方案一:严格的try-catch处理回想一下我们上一节介绍的所有核心过滤器,还记得有一个post过滤器SendErrorFilter用来处理异常信息吗?按照正常的处理流程,过滤器会处理异常信息,所以如果这里没有异常信息,很可能过滤器还没有执行。那么,我们仔细看看SendErrorFilter的shouldFilter函数:publicbooleanshouldFilter(){RequestContextctx=RequestContext.getCurrentContext();returnctx.containsKey("error.status_code")&&!ctx.getBoolean(SEND_ERROR_FILTER_RAN,false);}即可可见在这个方法ctx.containsKey("error.status_code")的返回值中有一个重要的判断依据,也就是说请求上下文中必须有一个error.status_code参数。我们实现的ThrowExceptionFilter中并没有设置这个参数,所以自然不会进入SendErrorFilter过滤器的处理逻辑。那么我们如何使用这个参数呢?我们可以看一下路由类型的几个过滤器。由于这些过滤器会发起外部请求,所以肯定有异常需要处理。例如RibbonRoutingFilter的run方法实现如下:(USresponse);returnresponse;}catch(Zuul_DE_STAext),ex.nStatusCode);context.set("error.message",ex.errorCause);context.set("error.exception",ex);}catch(Exceptionex){context.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);context.set("error.exception",ex);}returnnull;}可以看出整个请求的逻辑是通过一个try-赶上块。在catch异常的处理逻辑中,并没有输出操作,而是在请求上下文中加入了一些与错误相关的参数,主要包括以下三个参数:error.status_code:错误码error.exception:Exception异常对象error。message:错误信息其中,error.status_code参数是SendErrorFilter过滤器用来判断是否需要执行的重要参数。分析完这一点,实现异常处理的大致思路就清晰了。我们可以参考RibbonRoutingFilter的实现,对ThrowExceptionFilter的run方法做一些异常处理修改,如下:();}catch(Exceptione){ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);ctx.set("error.exception",e);}returnnull;}经过以上修改,我们尝试访问又是之前的界面。此时我们可以得到如下响应内容:{"timestamp":1481674980376,"status":500,"error":"InternalServerError","exception":"java.lang.RuntimeException","message":"Existssomeerrors..."}至此,我们的异常信息已经通过SendErrorFilter过滤器处理并返回给客户端,同时也在网关异常信息的控制台输出。从返回的响应信息中可以看到我们之前在requestcontext中设置的几个内容,它们的对应关系如下:status:对应error.status_code参数的值exception:对应error.exception参数:对应error.exception参数中Exception的消息信息。对于消息信息,我们也可以使用ctx.set("error.message","customexceptionmessage");在过滤器中定义更友好的错误消息。SendErrorFilter会优先将error.message作为返回的消息内容,如果不是,则会使用异常解决方案二:ErrorFilter处理中的消息信息通过以上的分析和实验,我们已经知道了如何在过滤器中正确处理异常,所以使得错误信息能够顺利流向后续的SendErrorFilter过滤器进行组织输出。但是,即使我们继续强调在过滤器中使用try-catch来处理业务逻辑并在请求上下文中添加异常信息,不可控的人为因素、不可预料的程序因素等,仍然会导致一些异常通过filter中抛出,对于意外抛出的异常,不会有控制台输出,也没有响应信息,那么有什么好的方法可以对这些异常做一个统一的处理呢?这时候,我们就可以使用错误类型过滤器了。由于在请求生命周期的pre、route、post阶段,当抛出异常时,会进入error阶段进行处理,所以我们可以通过创建error类型的过滤器来捕获这些异常信息,并根据这些异常information在请求上下文中注入需要返回给客户端的错误描述。这里我们可以直接使用try-catch中使用的error参数来处理异常信息,这样信息就可以被SendErrorFilter捕获并组织成消息响应返回给客户端。.例如,下面的代码实现了一个过滤器,如下所述:returntrue;}@OverridepublicObjectrun(){RequestContextctx=RequestContext.getCurrentContext();Throwablethrowable=ctx.getThrowable();log.error("thisisaErrorFilter:{}",throwable.getCause().getMessage());ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);ctx.set("error.exception",throwable.getCause());returnnull;}}在我们的APIGateway服务中加入这个过滤器后,我们可以尝试使用ThrowExceptionFilter(不包含异常处理机制的代码)在前面介绍try-catch处理时实现,以便过滤器可以抛出异常。这个时候我们会通过API网关来访问服务接口。此时我们可以在控制台看到ThrowExceptionFilter过滤器抛出的异常信息,在请求响应中也可以得到如下错误信息内容,而不是什么信息都没有。{"timestamp":1481674993561,"status":500,"error":"InternalServerError","exception":"java.lang.RuntimeException","message":"Existssomeerrors..."}【本文为专栏作者》翟永超原创稿件,转载请联系作者获得授权】点此查看该作者更多好文
