Spring Cloud Zuul中异常处理细节
Spring Cloud Zuul对异常的处理整体来说还是比较方便的,流程也比较清晰,只是由于Spring Cloud发展较快,各个版本之间有差异,导致有的小伙伴在寻找这方面的资料的时候经常云里雾里,本文将以Dalston.SR3版本为例,来说明Spring Cloud Zuul中的异常处理问题。
本文是Spring Cloud系列的第二十一篇文章,了解前二十篇文章内容有助于更好的理解本文:
1.使用Spring Cloud搭建服务注册中心
2.使用Spring Cloud搭建高可用服务注册中心
3.Spring Cloud中服务的发现与消费
4.Eureka中的核心概念
5.什么是客户端负载均衡
6.Spring RestTemplate中几种常见的请求方式
7.RestTemplate的逆袭之路,从发送请求到负载均衡
8.Spring Cloud中负载均衡器概览
9.Spring Cloud中的负载均衡策略
10.Spring Cloud中的断路器Hystrix
11.Spring Cloud自定义Hystrix请求命令
12.Spring Cloud中Hystrix的服务降级与异常处理
13.Spring Cloud中Hystrix的请求缓存
14.Spring Cloud中Hystrix的请求合并
15.Spring Cloud中Hystrix仪表盘与Turbine集群监控
16.Spring Cloud中声明式服务调用Feign
17.Spring Cloud中Feign的继承特性
18.Spring Cloud中Feign配置详解
19.Spring Cloud中的API网关服务Zuul
20.Spring Cloud Zuul中路由配置细节
首先我们来看一张官方给出的Zuul请求的生命周期图,如下:
关于这张图我说如下几点:
1.正常情况下所有的请求都是按照pre、route、post的顺序来执行,然后由post返回response
2.在pre阶段,如果有自定义的过滤器则执行自定义的过滤器
3.pre、routing、post的任意一个阶段如果抛异常了,则执行error过滤器,然后再执行post给出响应
这是这张图给我们的信息,我们再来看看源码com.netflix.zuul.http.ZuulServlet
类中的service方法,这是整个调用过程的核心,如下:
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方法的一部分核心代码,如下:
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 != null) throw 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对象中提取出来,核心代码如下:
@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;
}
如果我们想要自定义异常信息也很方便,如下:
@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,有问题欢迎留言讨论。
更多JavaEE资料请关注公众号: