1. 简单实现一个会抛异常的 ZuulFilter
public class TestZuulFilter extends ZuulFilter {
@Override
public String filterType() { // 以 pre 为例
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() { // 优先级
return FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
}
@Override
public boolean shouldFilter() { // 是否过滤
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println(1/0); // 会抛出异常
}
}
默认返回的异常信息内容及结构
{
"timestamp": 1585974019634,
"status": 500,
"error": "Internal Server Error",
"message": "pre:TestZuulFilter"
}
2. 异常处理:
@RestController
public class TestErrorController implements ErrorController {
/**
* zuul的异常处理
*
* @param request HTTP请求
* @return API统一响应
*/
@RequestMapping
public ResponseEntity error(HttpServletRequest request, HttpServletResponse response) {
Integer code = (Integer) request.getAttribute("javax.servlet.error.status_code");
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
String message = "服务器内部错误";
if (exception instanceof ZuulException) {
message = exception.getCause().getMessage();
}
response.setStatus(HttpStatus.OK.value());
return ResponseUtil.otherResponse(code, message);
}
@Override
public String getErrorPath() {
return "/error";
}
}
返回结果:
{
"status": 500,
"message": "/ by zero",
"data": {}
}
3. 通过源码分析为何这样处理
public class ZuulServlet extends HttpServlet {
。。。 //省略无关代码
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute(); // 执行"pre" 类型的filter
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
}
}
}
public class FilterProcessor {
。。。 //省略无关代码
public void preRoute() throws ZuulException {
try {
this.runFilters("pre");
} catch (ZuulException var2) {
throw var2;
} catch (Throwable var3) {
throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
}
}
}
首先请求会到 ZuulServlet ,然后调用 preRoute()方法执行 filterType为"pre" 类型的filter
public class FilterProcessor {
。。。 //省略无关代码
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); // 列出所有同类型的filter
if (list != null) {
for(int i = 0; i < list.size(); ++i) { // 轮训所有 filter
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= (Boolean)result;
}
}
}
return bResult;
}
// 该函数的作用是列出所有的同一类型的filter,然后进行排序
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
if (list != null) {
return list;
} else {
List<ZuulFilter> list = new ArrayList();
Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
Iterator iterator = filters.iterator();
while(iterator.hasNext()) {
ZuulFilter filter = (ZuulFilter)iterator.next();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
Collections.sort(list); // 这里就是排序过程
this.hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
}
sType 为定义 ZuulFilter 时的 filterType类型,runFilters方法会列出所有的 sType 类型的 filter,然后根据我们自定义的 filterOrder 进行排序,排序之后会依次执行各个filter的run方法获取执行结果,代码如下:
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
try {
..... // 省略部分代码
ZuulFilterResult result = filter.runFilter(); // filter执行run 方法的返回结果
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
switch(s) { // 获取filter的执行结果
case FAILED:
t = result.getException(); // 如果失败了,t 用于记录异常信息
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
if (bDebug) {
Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
Debug.compareContextState(filterName, copy);
}
}
if (t != null) {
throw t; // 如果失败了,t不为null,直接将异常抛出
} else {
this.usageNotifier.notify(filter, s);
return o;
}
} catch (Throwable var15) { // 捕获异常继续上抛
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage());
}
this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (var15 instanceof ZuulException) { // 如果是ZuulException的子类,强转一下抛出
throw (ZuulException)var15;
} else { // 如果不是,则固定格式抛出异常
ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
先runfilter() 看看结果是否正常,如果成功则 return 继续运行下一个filter,如果失败,则将异常抛出。注意,这里的异常如果不是ZuulException 会被定义为ZuulException然后上抛,抛到哪里进行处理呢?
public class ZuulServlet extends HttpServlet {
。。。 //省略无关代码
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute();
} catch (ZuulException var12) { // 这里进行处理
this.error(var12); // 获取前面抛出的异常,然后对异常进行处理
this.postRoute();
return;
}
}
}
}
异常在 ZuulServlet 中得到处理,zuul 支持定义错误filter的功能,对错误进行处理,原理和 proRoute()是一致的,当执行 this.error(); 会轮询所有 sType 为 error 的filter,其中 zuul 默认存在一个 SendErrorFilter 用于处理中间遇到的异常
public class SendErrorFilter extends ZuulFilter {
private static final Log log = LogFactory.getLog(SendErrorFilter.class);
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
@Value("${error.path:/error}") // 这里定义错误跳转的路径
private String errorPath;
public SendErrorFilter() {
}
public String filterType() {
return "error";
}
public int filterOrder() {
return 0;
}
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return ctx.getThrowable() != null && !ctx.getBoolean("sendErrorFilter.ran", false);
}
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
SendErrorFilter.ExceptionHolder exception = this.findZuulException(ctx.getThrowable());
HttpServletRequest request = ctx.getRequest();
request.setAttribute("javax.servlet.error.status_code", exception.getStatusCode());
log.warn("Error during filtering", exception.getThrowable());
request.setAttribute("javax.servlet.error.exception", exception.getThrowable());
if (StringUtils.hasText(exception.getErrorCause())) {
request.setAttribute("javax.servlet.error.message", exception.getErrorCause());
}
RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath);
if (dispatcher != null) {
ctx.set("sendErrorFilter.ran", true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.getStatusCode());
dispatcher.forward(request, ctx.getResponse());
}
}
} catch (Exception var5) {
ReflectionUtils.rethrowRuntimeException(var5);
}
return null;
}
。。。 //省略无关代码
代码可以看出,如果出现异常,SendErrorFilter 会将状态码和message 写入到 request 的属性中,然后 dispatcher.forward 直接跳转到定义的地址(默认为 /error)。
4. 总结
综上所述,如果需要自定义异常捕获,需要:
- 从 request 获取状态码及异常信息
- 覆盖原有 /error controller 的处理或者修改配置forward到其他自定义controller