知識庫 / Spring / Spring Cloud RSS 訂閱

自定義 Zuul 異常處理

Spring Cloud
HongKong
6
12:21 PM · Dec 06 ,2025

1. 概述

Zuul 是 Netflix 基於 JVM 的路由器和服務器端負載均衡器。 Zuul 的規則引擎提供了靈活性,可以編寫規則和過濾器來增強 Spring Cloud 微服務架構中的路由。

在本文中,我們將探討如何通過編寫 自定義錯誤過濾器,在代碼執行過程中發生錯誤時運行,來定製異常和錯誤響應。

2. Zuul 異常處理

所有在 Zuul 中處理的異常都是 ZuulExceptions。 讓我們明確的是,ZuulException 不能被 @ControllerAdvice 捕獲,並且通過 @ExceptionHandling 註解方法也無法捕獲。 這是因為 ZuulException 是從錯誤過濾器中拋出的。 因此,它會跳過後續的過濾器鏈,並不會到達錯誤控制器。 下圖描述了 Zuul 中的錯誤處理層次結構:

 

 

當 Zuul 發生 ZuulException 時,會顯示以下錯誤響應:
{
    "timestamp": "2022-01-23T22:43:43.126+00:00",
    "status": 500,
    "error": "Internal Server Error"
}

在某些情況下,我們可能需要自定義響應中 ZuulException 的錯誤消息或狀態碼。Zuul 過濾器將派上用場。在下一部分,我們將討論如何擴展 Zuul 的錯誤過濾器並自定義 ZuulException

3. 自定義 Zuul 異常

<em>spring-cloud-starter-netflix-zuul</em> 的啓動包包含三種類型的過濾器:預處理過濾器、後處理過濾器和錯誤過濾器。這裏,我們將深入探討錯誤過濾器,並探索自定義名為 SendErrorFilter 的 Zuul 錯誤過濾器

首先,我們將禁用自動配置的默認 SendErrorFilter,從而避免擔心執行順序,因為這確實是唯一的 Zuul 默認錯誤過濾器。 讓我們在 application.yml 中添加屬性以禁用它:

zuul:
  SendErrorFilter:
    post:
      disable: true

現在,讓我們編寫一個自定義 Zuul 錯誤過濾器,名為 CustomZuulErrorFilter,如果底層服務不可用,則拋出自定義異常:

public class CustomZuulErrorFilter extends ZuulFilter {
}

此自定義過濾器需要擴展 com.netflix.zuul.ZuulFilter 並覆蓋其中的一些方法。

首先,我們需要覆蓋 filterType() 方法並返回類型為“error”。這是因為我們希望將 Zuul 過濾器配置為錯誤過濾器類型:

@Override
public String filterType() {
    return "error";
}

之後,我們覆蓋filterOrder()並返回-1,以便過濾器在鏈中成為第一個。

@Override
public int filterOrder() {
    return -1;
}

然後,我們覆蓋shouldFilter()方法,並無條件地返回true,因為我們希望在所有情況下鏈接使用此過濾器:

@Override
public boolean shouldFilter() {
    return true;
}

最後,讓我們覆蓋 run()方法

@Override
public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    Throwable throwable = context.getThrowable();

    if (throwable instanceof ZuulException) {
        ZuulException zuulException = (ZuulException) throwable;
        if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
            context.remove("throwable");
            context.setResponseBody(RESPONSE_BODY);
            context.getResponse()
                .setContentType("application/json");
            context.setResponseStatusCode(503);
        }
    }
    return null;
}

讓我們分解一下 run() 方法,以瞭解它的工作原理。首先,我們獲取 RequestContext 的實例。然後,我們驗證從 RequestContext 獲得的 throwable 是否是 ZuulException 的實例。接下來,我們檢查嵌套 throwable 的原因是否是 ConnectException 的實例。最後,我們設置了帶有自定義屬性的上下文。

請注意,在設置自定義響應之前,我們清除上下文中的 throwable,以防止後續過濾器中的進一步錯誤處理

此外,我們還可以設置在 run() 方法中可以由後續過濾器處理的自定義異常:

if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
    ZuulException customException = new ZuulException("", 503, "Service Unavailable");
    context.setThrowable(customException);
}

上述片段將記錄堆棧跟蹤並繼續執行下一個過濾器。

此外,我們可以修改此示例以在 ZuulFilter 中處理多個異常。

4. 測試自定義 Zuul 異常

本節將測試自定義 Zuul 異常,具體測試我們的 CustomZuulErrorFilter

假設存在 ConnectException,上述示例在 Zuul API 的響應中將產生如下結果:

{
    "timestamp": "2022-01-23T23:10:25.584791Z",
    "status": 503,
    "error": "Service Unavailable"
}

此外,我們還可以隨時更改默認的 Zuul 錯誤轉發路徑,通過在 application.yml 文件中配置 error.path 屬性來實現。

現在,讓我們通過一些測試用例進行驗證:

@Test
public void whenSendRequestWithCustomErrorFilter_thenCustomError() {
    Response response = RestAssured.get("http://localhost:8080/foos/1");
    assertEquals(503, response.getStatusCode());
}

在上述測試場景中,為了保持 /foos/1 路由的暢通,故意將其保留下來,從而導致 java.lang.ConnectException。因此,我們的自定義過濾器會攔截並返回 503 狀態碼。

現在,讓我們在未註冊自定義錯誤過濾器的情況下進行測試:

@Test
public void whenSendRequestWithoutCustomErrorFilter_thenError() {
    Response response = RestAssured.get("http://localhost:8080/foos/1");
    assertEquals(500, response.getStatusCode());
}

執行上述測試用例,且未註冊自定義錯誤過濾器,會導致 Zuul 返回狀態碼 500。

5. 結論

在本教程中,我們學習了錯誤處理的層次結構,並深入瞭解了在 Spring Zuul 應用程序中配置自定義 Zuul 錯誤過濾器的過程。該錯誤過濾器提供了自定義響應體以及響應碼的機會。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.