1. 概述
在本簡短教程中,我們將討論如何在 ResponseErrorHandler 接口上實現並注入它,以便在將 HTTP 錯誤返回的遠程 API 中,以優雅的方式處理這些錯誤,在 RestTemplate 實例中。
2. 默認錯誤處理
默認情況下,<em >RestTemplate</em> 在發生 HTTP 錯誤時會拋出以下異常之一:
- `HttpClientErrorException` – 在 HTTP 狀態碼為 4xx 的情況下
- `HttpServerErrorException` – 在 HTTP 狀態碼為 5xx 的情況下
- `UnknownHttpStatusCodeException` – 在 HTTP 狀態碼未知的情況下
所有這些異常都是 <em >RestClientResponseException</em> 的擴展。
顯然,添加自定義錯誤處理的最簡單策略是將調用包裝在 <em >try/catch</em> 塊中。然後我們可以根據需要處理捕獲的異常。
然而,這種簡單策略隨着遠程 API 或調用的數量增加時,無法擴展。如果我們能夠為所有遠程調用實現一個可重用的錯誤處理程序,那將更有效。
3. 實現 ResponseErrorHandler
一個實現 ResponseErrorHandler 的類將讀取響應的 HTTP 狀態碼,並根據以下情況之一進行操作:
- 拋出一個對我們的應用程序有意義的異常
- 簡單地忽略 HTTP 狀態碼,讓響應流在不中斷的情況下繼續
我們需要將我們的 ResponseErrorHandler 實現注入到 RestTemplate 實例中。為此,我們使用 RestTemplateBuilder 來替換響應流中的默認錯誤處理程序。
3.1. 處理常見HTTP錯誤
首先,我們實現一個簡單的 <em >RestTemplateResponseErrorHandler</em>,用於處理常見的HTTP錯誤,例如 <em >4xx</em> 和 <em >5xx</em> 狀態碼。例如,我們通過拋出自定義的 <em >NotFoundException</em> 來處理 <em >404</em> 錯誤:
@Component
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return httpResponse.getStatusCode().is5xxServerError() ||
httpResponse.getStatusCode().is4xxClientError();
}
@Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().is5xxServerError()) {
//Handle SERVER_ERROR
throw new HttpClientErrorException(httpResponse.getStatusCode());
} else if (httpResponse.getStatusCode().is4xxClientError()) {
//Handle CLIENT_ERROR
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}然後我們可以使用 RestTemplateBuilder 構建 RestTemplate 實例,並引入我們的 RestTemplateResponseErrorHandler:
@Service
public class BarConsumerService {
private RestTemplate restTemplate;
@Autowired
public BarConsumerService(RestTemplateBuilder restTemplateBuilder) {
RestTemplate restTemplate = restTemplateBuilder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
}
public Bar fetchBarById(String barId) {
return restTemplate.getForObject("/bars/4242", Bar.class);
}
}3.2. 處理 401 未授權並解析響應體
有時,當 API 返回 401 未授權 狀態時,響應體可能包含有用的信息,例如我們可能想要提取和使用的錯誤消息。
由於 ResponseErrorHandler.handleError() 方法不自動反序列化響應體,一種常見的方法是 捕獲 HttpStatusCodeException,該異常由 RestTemplate 拋出,並手動提取體 。為了更有效地處理 401 未授權狀態,我們可以將請求包裝在 try-catch 塊中,並在異常被拋出時檢查響應體:
public Bar fetchBarById(String barId) {
try {
return restTemplate.getForObject("/bars/" + barId, Bar.class);
} catch (HttpStatusCodeException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
String responseBody = e.getResponseBodyAsString();
throw new UnauthorizedException("Unauthorized access: " + responseBody);
}
throw e;
}
}這種方法允許我們以一種受控和信息化的方式處理 401 未授權 錯誤。通過捕獲異常並手動提取響應體,我們可以檢查服務器返回的任何錯誤詳情,例如指示身份驗證失敗的消息或解決問題的説明。這使我們能夠向用户提供更清晰的反饋,為故障排除記錄更詳細的消息,甚至根據響應內容觸發備用機制。
4. 測試我們的實現
最後,我們將通過模擬服務器並返回 NOT_FOUND 狀態碼來測試此處理程序:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { NotFoundException.class, Bar.class })
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {
@Autowired
private MockRestServiceServer server;
@Autowired
private RestTemplateBuilder builder;
@Test
public void givenRemoteApiCall_when404Error_thenThrowNotFound() {
Assertions.assertNotNull(this.builder);
Assertions.assertNotNull(this.server);
RestTemplate restTemplate = this.builder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
this.server
.expect(ExpectedCount.once(), requestTo("/bars/4242"))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
Assertions.assertThrows(NotFoundException.class, () -> {
Bar response = restTemplate.getForObject("/bars/4242", Bar.class);
});
}
}5. 結論
在本文中,我們展示了一種實現和測試針對 RestTemplate 的自定義錯誤處理器的解決方案,該方案可以將 HTTP 錯誤轉換為有意義的異常。