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