Spring Cloud Zuul对异常的处理整体来说还是比较方便的,流程也比较清晰,只是由于Spring Cloud发展较快,各个版本之间有差异,导致有的小伙伴在寻找这方面的资料的时候经常云里雾里,本文将以Dalston.SR3版本为例,来说明Spring Cloud Zuul中的异常处理问题。
首先我们来看一张官方给出的Zuul请求的生命周期图,如下:
关于这张图我说如下几点:
-
正常情况下所有的请求都是按照pre、route、post的顺序来执行,然后由post返回response
-
在pre阶段,如果有自定义的过滤器则执行自定义的过滤器
-
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 !=
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对象中提取出来,核心代码如下:
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,有问题欢迎留言讨论。