使用OpenFeign的五個步驟和七個高級功能
什么是Feign?
Feign是一個開源的Java庫,用于簡化Web請求的過程。
它通過提供更高級別的抽象來簡化RESTful Web服務的實現,消除了樣板代碼的需要,使代碼庫更具可讀性和可維護性。
Feign是一個流行的Java HTTP客戶端庫,具有多種優勢和特性,是開發基于HTTP的微服務和應用程序的不錯選擇。
什么是聲明式HTTP客戶端?
聲明式HTTP客戶端是一種通過編寫Java接口來發起HTTP請求的方式。
Feign會根據我們提供的注解,在接口背后生成實際的實現。
為何使用Feign?
如果我們需要調用大量的API,手動編寫HTTP代碼或使用難以維護的代碼生成方式并不理想。
使用Feign,我們可以通過一個簡單的小接口來描述API,讓Feign在運行時解釋和實現該接口,這樣更加簡便且易于維護。
誰適合使用Feign?
如果我們在Java代碼中需要發起HTTP請求,并且不想編寫樣板代碼,或者不想直接使用像Apache httpclient這樣的庫,那么Feign是一個很好的選擇。
創建基本的Feign客戶端
步驟0:添加bom
我們依然使用feign-bom作為版本管理:
<project>
……
<properties>
<openfeign.version>13.4</openfeign.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-bom</artifactId>
<version>${openfeign.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
步驟1:添加Feign依賴
引入feign-core:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
步驟2:定義客戶端接口
該接口通常包含帶有Feign注解的方法聲明。
我們將為服務器上要調用的每個REST端點聲明一個客戶端接口方法,這些只是聲明,無需實現這些方法,Feign會為我們完成。方法簽名應包括HTTP方法以及所有必需的數據。
假設我們要定義一個表示計算器服務的接口,它有執行加法、減法、乘法和除法等計算的簡單API方法:
public interface CalculatorService {
/**
* 兩整數相加。
*
* @param firstNumber 第一個整數
* @param secondNumber 第二個整數
* @return 兩數之和
*/
@RequestLine("POST /operations/add?firstNumber={firstNumber}&secondNumber={secondNumber}")
Long add(@Param("firstNumber") Long firstNumber,
@Param("secondNumber") Long secondNumber);
/**
* 兩整數相減。
*
* @param firstNumber 第一個整數
* @param secondNumber 第二個整數
* @return 兩數之差
*/
@RequestLine("POST /operations/subtract?firstNumber={firstNumber}&secondNumber={secondNumber}")
Long subtract(@Param("firstNumber") Long firstNumber,
@Param("secondNumber") Long secondNumber);
/**
* 兩整數相乘。
*
* @param firstNumber 第一個整數
* @param secondNumber 第二個整數
* @return 兩數之積
*/
@RequestLine("POST /operations/multiply?firstNumber={firstNumber}&secondNumber={secondNumber}")
Long multiply(@Param("firstNumber") Long firstNumber,
@Param("secondNumber") Long secondNumber);
/**
* 兩整數相除。
*
* @param firstNumber 第一個整數
* @param secondNumber 第二個整數,不應為零
* @return 兩數之商
*/
@RequestLine("POST /operations/divide?firstNumber={firstNumber}&secondNumber={secondNumber}")
Long divide(@Param("firstNumber") Long firstNumber,
@Param("secondNumber") Long secondNumber);
}
@RequestLine
定義了請求的HTTP方法和url模板,而@Param
定義了一個模板變量。
?
不用擔心,稍后我們將詳細了解OpenFeign提供的注解。
步驟3:創建客戶端對象
我們使用Feign的builder()方法來準備客戶端:
final CalculatorService target = Feign
.builder()
.decoder(new JacksonDecoder())
.target(CalculatorService.class, HOST);
準備客戶端的方式有很多種,具體取決于我們的需求。
上面給出的代碼片段只是準備客戶端的一種簡單方式。
我們注冊了用于解碼JSON響應的解碼器,解碼器可以根據服務返回的響應的內容類型進行更改,稍后我們將詳細了解解碼器。
步驟4:使用客戶端進行API調用
現在讓我們調用客戶端的add()方法:
final Long result = target.add(firstNumber, secondNumber);
與其他HTTP客戶端相比,使用Feign HTTP客戶端調用服務相當簡單。
Feign注解
OpenFeign使用一組注解來定義HTTP請求及其參數,常用的OpenFeign注解及其示例如下:
注解 | 描述 | 示例 |
@RequestLine | 指定HTTP方法和路徑 | @RequestLine("GET /resource/{id}") |
@Headers | 指定請求的HTTP頭 | @Headers("Authorization: Bearer {token}") |
@QueryMap | 將查詢參數映射到請求 | @QueryMap Map<String, Object> queryParams |
@Body | 發送特定對象作為請求體 | @Body RequestObject requestObject |
@Param | 向請求添加查詢參數 | @Param("id") long resourceId |
@Path | 替換路徑中的模板變量 | @Path("id") long resourceId |
@RequestHeader | 向請求添加頭 | @RequestHeader("Authorization") String authToken |
@Headers | 指定請求的其他頭 | @Headers("Accept: application/json") |
這些注解可以用來定義OpenFeign客戶端接口,使其易于使用OpenFeign與遠程服務進行交互。我們可以根據特定的API要求混合和匹配這些注解。
處理響應
Feign還提供了一種聲明式的API集成方法。
與手動編寫處理響應或錯誤的樣板代碼不同,Feign允許我們定義自定義處理程序并將其注冊到Feign構建器中。
這不僅減少了我們需要編寫的代碼量,還提高了可讀性和可維護性。
讓我們看一個解碼器的示例:
final CalculatorService target = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(CalculatorService.class, HOST);
這段給定的代碼片段展示了創建一個Feign客戶端,使用Jackson進行請求編碼和響應解碼:
- encoder(new JacksonEncoder()):在這里,為Feign客戶端設置了一個JacksonEncoder。JacksonEncoder是Feign Jackson模塊的一部分,用于將Java對象編碼為JSON格式的HTTP請求體。當需要在請求體中發送對象時,這特別有用。
- decoder(new JacksonDecoder()):同樣,為Feign客戶端設置了一個JacksonDecoder。JacksonDecoder負責將服務器的JSON響應解碼為Java對象,它將JSON響應反序列化為相應的Java對象。
處理錯誤
錯誤處理是構建健壯可靠應用程序的關鍵方面,尤其是在進行遠程API調用時。Feign提供了強大的功能來幫助有效地處理錯誤。
Feign讓我們對處理意外響應有更多的控制。我們可以通過構建器注冊一個自定義的ErrorDecoder。
final CalculatorService target = Feign.builder()
.errorDecoder(new CalculatorErrorDecoder())
.target(CalculatorService.class, HOST);
以下是一個錯誤處理的示例:
public class CalculatorErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
ExceptionMessage message = null;
try (InputStream bodyIs = response.body().asInputStream()) {
ObjectMapper mapper = new ObjectMapper();
message = mapper.readValue(bodyIs, ExceptionMessage.class);
} catch (IOException e) {
return new Exception(e.getMessage());
}
final String messageStr = message == null? "" : message.getMessage();
switch (response.status()) {
case 400:
return new RuntimeException(messageStr.isEmpty()
? "Bad Request"
: messageStr
);
case 401:
return new RetryableException(response.status(),
response.reason(),
response.request().httpMethod(),
null,
response.request());
case 404:
return new RuntimeException(messageStr.isEmpty()
? "Not found"
: messageStr
);
default:
return defaultErrorDecoder.decode(methodKey, response);
}
}
}
所有HTTP狀態不在HTTP 2xx范圍內的響應,例如HTTP 400,都將觸發ErrorDecoder的decode()方法。
在這個重寫的decode()方法中,我們可以處理響應,將失敗包裝成自定義異常或執行任何其他處理。
我們甚至可以通過拋出RetryableException來再次重試請求,這將調用注冊的Retryer。Retryer將在高級技術部分詳細解釋。
高級技術
集成編碼器/解碼器
編碼器和解碼器分別用于對請求和響應數據進行編碼/解碼。我們根據請求和響應的內容類型選擇它們,例如,對于JSON數據可以使用Gson或Jackson。
以下是一個使用Jackson編碼器和解碼器的示例:
final CalculatorService target = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(CalculatorService.class, HOST);
更改HTTP客戶端
默認情況下,Feign使用其自帶的HTTP客戶端。將Feign的默認HTTP客戶端從原始的Apache HTTP Client更改為其他庫(如OkHttp)的主要動機是為了獲得更好的性能、改進的功能以及與現代HTTP標準更好的兼容性。
現在讓我們看看如何覆蓋HTTP客戶端:
final CalculatorService target = Feign.builder()
.client(new OkHttpClient())
.target(CalculatorService.class, HOST);
配置日志記錄器
SLF4JModule用于將Feign的日志記錄發送到SLF4J。通過SLF4J,我們可以輕松使用我們選擇的日志后端(Logback、Log4J等)。
以下是一個構建客戶端的示例:
final CalculatorService target = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Level.FULL)
.target(CalculatorService.class, HOST);
要在Feign中使用SLF4J,需要將SLF4J模塊和我們選擇的SLF4J綁定添加到類路徑中,然后按照上述方式配置Feign使用Slf4jLogger。
配置請求攔截器
Feign中的請求攔截器允許我們在將HTTP請求發送到遠程服務器之前對其進行自定義和操作。它們可用于多種目的,例如添加自定義頭、日志記錄、身份驗證或請求修改。
以下是我們可能希望在Feign中使用請求攔截器的原因:
- 身份驗證:我們可以使用請求攔截器向每個請求添加身份驗證令牌或憑據,例如添加帶有JWT令牌的“Authorization”頭。
- 日志記錄:攔截器有助于記錄傳入和傳出的請求和響應,這對于調試和監控很有用。
- 請求修改:我們可以在發送請求之前修改請求,包括更改頭、查詢參數甚至請求體。
- 速率限制:通過檢查發出的請求數量來決定是否允許或阻止請求,從而實現速率限制。
- 緩存:根據特定標準緩存請求/響應數據。
以下是一個演示如何使用請求攔截的代碼片段:
static class AuthorizationInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 檢查令牌是否存在,如果不存在則添加它
template.header("Authorization", "Bearer " + generatedToken);
}
}
public class CalculatorServiceTest {
public static void main(String[] args) {
final AuthorizationInterceptor interceptor = new AuthorizationInterceptor();
final CalculatorService target = Feign.builder()
.requestInterceptor(interceptor)
.target(CalculatorService.class, HOST);
}
}
實現RequestInterceptor并覆蓋其apply()方法,以根據需要對請求進行任何修改。
配置重試器
OpenFeign Retryer是一個組件,允許我們配置Feign在請求失敗時如何處理重試。它對于處理網絡通信中的瞬時故障特別有用,我們可以指定Feign應自動重試失敗請求的條件。
重試器配置
要在OpenFeign中使用重試器,需要提供Retryer接口的實現。Retryer接口有兩個方法:
- boolean continueOrPropagate(int attemptedRetries, int responseStatus, Request request):此方法用于確定是繼續重試還是傳播錯誤。它接受嘗試重試的次數、HTTP響應狀態和請求作為參數,并返回true以繼續重試或false以傳播錯誤。
- Retryer clone():此方法創建重試器實例的克隆。
默認重試器
Feign提供了一個默認的重試器實現,名為Retryer.Default。當我們創建Feign客戶端而未顯式指定自定義重試器時,將使用此默認重試器。
它提供了兩個工廠方法來創建Retryer對象。
第一個工廠方法不需要任何參數:
public Default() {
this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
它定義了一個簡單的重試策略,具有以下特點:
- 最大嘗試次數:對于失敗的請求,最多允許5次重試嘗試。
- 退避期:在重試之間使用指數退避策略,從100毫秒的退避開始,每次后續重試將退避時間加倍。
- 可重試異常:如果請求導致任何被認為可重試的異常(通常包括網絡相關異常,如連接超時或套接字異常),則會重試請求。
第二個工廠方法需要一些參數。如果默認配置不適合我們,可以使用它:
public Default(long period, long maxPeriod, int maxAttempts)
// 使用它創建重試器
new Retryer.Default(1, 100, 10);
雖然Feign提供的默認重試器涵蓋了許多常見的重試場景,但在某些情況下,我們可能希望定義自定義重試器。以下是定義自定義重試器的一些動機:
- 精細控制:如果我們需要對默認重試行為進行更多控制,例如指定不同的最大重試次數或自定義退避策略,自定義重試器允許我們根據特定要求定制行為。
- 重試邏輯:在某些情況下,我們可能只希望針對特定的響應代碼或異常重試請求。自定義重試器允許我們實現自己的邏輯來確定何時應該進行重試。
- 日志記錄和指標:如果我們想要記錄或收集與重試嘗試相關的指標,實現自定義重試器提供了添加此功能的機會。
- 與斷路器集成:如果我們將斷路器模式與Feign結合使用,自定義重試器可以與斷路器的狀態集成,以便在決定何時重試或何時打開斷路器時做出更明智的決策。
- 非標準重試策略:對于不適合默認重試器提供的標準重試策略的場景,例如速率限制的API或具有特定重試要求的API,我們可以定義適合我們用例的自定義重試器。
以下是在OpenFeign中實現自定義Retryer的示例:
public class CalculatorRetryer implements Retryer {
/**
* 重試之間等待的毫秒數
*/
private final long period;
/**
* 最大重試次數
*/
private final int maxAttempts;
private int attempt = 1;
@Override
public void continueOrPropagate(RetryableException e) {
log.info("Feign retry attempt {} of {} due to {}",
attempt,
maxAttempts,
e.getMessage());
if (++attempt > maxAttempts) {
throw e;
}
if (e.status() == 401) {
try {
Thread.sleep(period);
} catch (InterruptedException ex) {
throw e;
}
} else {
throw e;
}
}
@Override
public Retryer clone() {
return this;
}
public int getRetryAttempts() {
return attempt - 1; // 減去1以排除初始嘗試
}
}
它專門重試HTTP 401錯誤。
總之,當我們需要對HTTP請求中的重試處理方式有更大的控制權和靈活性時,就有必要在Feign中創建自定義重試器。當我們的需求與默認重試器的行為不同時,自定義重試器允許我們根據特定用例修改重試邏輯。
斷路器
斷路器通常使用單獨的庫或工具(如Netflix Hystrix、Resilience4j或Spring Cloud Circuit Breaker)來實現。
為何使用斷路器?
在Feign中使用斷路器的主要動機是增強基于微服務的應用程序的彈性。以下是一些關鍵原因:
- 故障隔離:斷路器通過隔離故障組件,防止一個服務中的故障級聯到其他服務。
- 快速失敗:當電路打開(表示故障狀態)時,后續請求會“快速失敗”,而不會嘗試向可能無響應或故障的服務發出調用,從而減少延遲和資源消耗。
- 優雅降級:當依賴服務出現問題時,斷路器允許應用程序優雅降級,確保它能夠繼續提供一組簡化的功能。
- 監控和指標:斷路器提供指標和監控功能,使我們能夠跟蹤服務的健康狀況和性能。
配置斷路器
HystrixFeign用于配置Hystrix提供的斷路器支持。
Hystrix是一個延遲和容錯庫,旨在隔離分布式環境中對遠程系統、服務和第三方庫的訪問點。它有助于阻止級聯故障,并在故障不可避免的復雜分布式系統中實現彈性。
要在Feign中使用Hystrix,需要將Hystrix模塊添加到類路徑中,并使用HystrixFeign構建器,如下所示:
final CalculatorService target = HystrixFeign.builder()
.target(CalculatorService.class, HOST);
讓我們看看如何使用回退類來處理服務錯誤。
在Hystrix中,回退類是為Hystrix命令定義回退邏輯的另一種方式,而不是直接在Hystrix命令類的getFallback方法中定義回退邏輯。回退類提供了關注點分離,使我們能夠將命令類專注于主要邏輯,并將回退邏輯委托給單獨的類。這可以提高代碼的組織性和可維護性。
以下是為CalculatorService實現回退的示例代碼:
@Slf4j
public class CalculatorHystrixFallback implements CalculatorService {
@Override
public Long add(Long firstNumber, Long secondNumber) {
log.info("[Fallback add] Adding {} and {}", firstNumber, secondNumber);
return firstNumber + secondNumber;
}
@Override
public Long subtract(Long firstNumber, Long secondNumber) {
return null;
}
@Override
public Long multiply(Long firstNumber, Long secondNumber) {
return null;
}
@Override
public Long divide(Long firstNumber, Long secondNumber) {
return null;
}
}
為了演示回退,我們僅實現了add方法。然后在構建客戶端時使用此回退:
final CalculatorHystrixFallback fallback = new CalculatorHystrixFallback();
final CalculatorService target = HystrixFeign.builder()
.decoder(new JacksonDecoder())
.target(CalculatorService.class,
HOST, fallback);
當add端點返回錯誤或電路打開時,Hystrix將調用add回退方法。
收集指標
Feign本身不像其他一些庫或框架那樣提供內置的指標功能API。
與Feign相關的指標,如請求持續時間、錯誤率或重試次數,通常需要使用外部庫或工具來收集和跟蹤。
在Java應用程序中收集指標的流行庫包括Micrometer和Dropwizard Metrics。
以下是如何使用常用的Micrometer庫來收集和報告與Feign調用相關的指標:
public class CalculatorServiceTest {
public static void main(String[] args) {
final CalculatorService target = Feign.builder()
.addCapability(new MicrometerCapability())
.target(CalculatorService.class, HOST);
target.contributors("OpenFeign", "feign");
// 從此時起指標將可用
}
}
請注意,我們需要將Micrometer作為依賴項添加到項目中并進行適當配置。
文末總結
本文算是溫故知新的一篇,由淺入深介紹了使用OpenFeign的5個步驟和7個高級功能,OpenFeign的使用方面,已經涵蓋全了。后續再看看在SpringCloud中,OpenFeign的使用,以及在于Ribbon等組件結合使用時,又能碰撞出哪些火花。