【Spring Boot】第14.2課-使用 OpenFeign 存取外部 API
本文最後更新於:2025-10-15
最近的工作任務,需要將資料拋送到另一個系統中。根據筆者以前的工作經驗,覺得應該會用到第 14.1 課介紹的 RestTemplate
。然而發現同事都是使用 Spring Cloud 中的「OpenFeign」來存取遠端服務。
Spring Cloud 是一個大型框架,集合了許多關於微服務的功能。本文介紹的 OpenFeign,用途就是與外部服務通訊。它允許我們結合 Controller 中會用到的注解,因此使用方式比 RestTemplate
更加直覺。
一、認識 Reqres 服務
本文同樣使用「Reqres」這個免費服務,來串接範例的 RESTful API,它會回傳假資料。

從上圖中,可看到 GET https://reqres.in/api/users/2
這支 API 的用法。
從 Reqres 的官方網頁繼續往下看,還能找到它的 Swagger 文件網頁。

二、程式專案準備
(一)引入 library
首先請根據程式專案的 Spring Boot 版本,挑選好 Spring Cloud 版本。我們可參考官方文件的對照表。

在對照表中,找到 Spring Boot 版本後,點進左邊欄位的連結,可察看 Spring Cloud 的 Release Note。在此能找到各種 Spring Cloud 的確切版本號。

假設 Spring Boot 版本是 3.5.6,那麼本文最新可選擇的版本是 2025.0.0。
請在 pom.xml 檔案中「dependencyManagement」的「dependencies」區塊中,添加 Spring Cloud 及其版本。
<project ...>
<!--略-->
<dependencyManagement>
<dependencies>
<!--略-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2025.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Spring Cloud 底下有許多功能可引入到程式專案中。為了讓彼此之間相容,需透過 POM 的 dependency management 功能,來抓取適合的 library 版本。
確定好 Spring Cloud 版本後,我們才添加底下功能的 library。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
此處不必指定 OpenFeign 的版本,交由 POM 的 dependency management 功能來抓取即可。
(二)準備測試程式
讀者可另外準備測試類別,以便練習呼叫 OpenFeign 來存取外部 API。
@SpringBootTest
class OpenFeignTests {
private static final ObjectMapper om = new ObjectMapper();
// TODO
private void printJSON(Object obj) {
try {
String json = om.writeValueAsString(obj);
System.out.println(json);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
測試類別會透過 Spring Boot 提供的 ObjectMapper
,將 response body 轉換成 JSON 字串,並印在 console 視窗。
三、使用 OpenFeign 進行呼叫
(一)宣告介面
使用 OpenFeign 時,我們會宣告一個介面,藉此定義要呼叫哪個服務的哪些 API。
@FeignClient(name = "reqres-feign", url = "https://reqres.in/api")
public interface ReqresFeignClient {
// TODO
}
該介面冠上了 @FeignClient
注解,並傳入兩個參數。name
是定義這個呼叫介面的名稱;url
則定義服務的 API endpoint 前綴。
url
參數也可在 application.properties 檔案配置好,再透過 ${}
的語法來取用。若外部服務的主機,在測試與正式環境的 API 前綴不同,便可使用此做法。
在 Spring Boot 的啟動類別,需冠上 @EnableFeignClients
注解,上述的 OpenFeign 介面才會生效。
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
下面的例子,會以 Reqres 網站提供的 API 為例,來示範串接。
(二)定義呼叫方法
以 /users/2
這支 API 為例,它的用途是取得 id 為 2 的使用者資料。Response body 為:
{
"data": {
"id": 2,
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://reqres.in/img/faces/2-image.jpg"
},
"support": {
"url": "https://contentcaddy.io?utm_source=reqres&utm_medium=json&utm_campaign=referral",
"text": "Tired of writing endless social media content? Let Content Caddy generate it for you."
}
}
以下的類別是用來接收 response body:
public class SingleUserResponse {
private UserResponse data;
// getter, setter ...
}
import com.fasterxml.jackson.annotation.JsonProperty;
public class UserResponse {
private int id;
private String email;
@JsonProperty("first_name")
private String firstName;
@JsonProperty("last_name")
private String lastName;
// getter, setter ...
}
在 OpenFeign 介面中,需宣告對應的方法,來指向這支 API。
public interface ReqresFeignClient {
@GetMapping("/users/{id}")
SingleUserResponse getUserById(
@RequestHeader("x-api-key") String apiKey,
@PathVariable("id") int userId
);
}
透過平常在 Controller 使用的 @GetMapping
、@PathVariable
等注解,我們可輕鬆地定義好 API 的呼叫方式。而 API 的 response body 也會被轉換成方法回傳類別的物件。
附帶一提,Reqres 網站的 API 規定要攜帶指定的 request header(x-api-key: reqres-free-v1
),因此在下面的範例程式中也一併提供。
(三)實際呼叫
啟動程式時,Spring Boot 會建立出 OpenFeign 介面的元件,讀者可將它注入到需要的地方。
@SpringBootTest
class OpenFeignTests {
// ...
@Autowired
private ReqresFeignClient client;
@Test
void testGetUserById() {
SingleUserResponse user = client.getUserById("reqres-free-v1", 2);
printJSON(user);
}
}
存取外部服務時,只要呼叫 OpenFeign 介面的方法即可,並隨即運用回傳值,十分簡潔。
(四)攜帶 query string 與 request body
在定義 OpenFeign 介面時,我們也能透過注解的方式,來攜帶 query string 與 request body。
public interface ReqresFeignClient {
@GetMapping("/users")
ListUserResponse getUsers(
@RequestHeader("x-api-key") String apiKey,
@RequestParam("page") int page,
@RequestParam("per_page") int size
);
@PostMapping("/users")
CreateUserResponse createUser(
@RequestHeader("x-api-key") String apiKey,
@RequestBody CreateUserRequest request
);
}
範例程式中的 request 與 response body 類別,與第 14.1 課大同小異。讀者可參考文末的完成後專案及測試程式,在此不贅述。
(五)取得更完整的 response 內容
若讀者還想取得 response 的 HTTP 狀態碼、header 等資料,可使用 ResponseEntity
做為 OpenFeign 介面方法的回傳值。示意如下:
public interface ReqresFeignClient {
@GetMapping("/users/{id}")
ResponseEntity<SingleUserResponse> getUserById(
@RequestHeader("x-api-key") String apiKey,
@PathVariable("id") int userId
);
}
ResponseEntity
也是平常在 Controller 中會用到的東西,從中便可取出各種 response 相關的資料了。
四、額外配置
(一)配置類別
本文接下來會介紹可以對 OpenFeign 做的其他配置。請宣告一個配置類別,但不需要冠上 @Configuration
注解,如下:
public class ReqresFeignConfig {
}
接著將此類別傳入 OpenFeign 介面的 @FeignClient
注解的 configuration
參數。
@FeignClient(
name = "reqres-feign",
url = "https://reqres.in/api",
configuration = ReqresFeignConfig.class)
public interface ReqresFeign {
// ...
}
這個類別會存放各種建立配置元件的方法,供 OpenFeign 介面讀取。
(二)配置 HTTP Client
OpenFeign 預設使用的 client 是 JDK 的 HttpURLConnection
,我們可以換成「OkHttp」,得到更好的性能。
首先在 pom.xml 檔案中引入 library。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
接著在配置類別準備好 OkHttpClient
的元件。
import okhttp3.OkHttpClient;
public class ReqresFeignConfig {
@Bean
public OkHttpClient client() {
return new OkHttpClient.Builder()
.callTimeout(20, TimeUnit.SECONDS)
.build();
}
}
此處還另外配置了連線逾時時間。
(三)配置攔截器(interceptor)
透過攔截器,我們可以在發送 request 前,固定做一些想要的事。以下的例子是添加 header,並在 console 視窗印出呼叫的 API。
public class ReqresFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
Request request = requestTemplate.request();
System.out.println(request.httpMethod() + " " + request.url());
requestTemplate.header("x-api-key", "reqres-free-v1");
};
}
}
如此一來,原本在 OpenFeign 介面方法的 request header 參數,便能移除了。
(四)配置 HTTP Basic 認證
有些外部 API 是受到保護的,存取時需認證呼叫方的身份。若該 API 是採用 HTTP Basic 認證,那麼存取時就得將帳密經過 Base64 編碼,將產生的 token 值添加到 request header。
OpenFeign 提供了元件,讓我們能直接提供帳密明文,並在發送 request 時自動產生 token 值,添加到 header 上。
public class ReqresFeignConfig {
// ...
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(
@Value("${your-username}") String username,
@Value("${your-password}") String password
) {
return new BasicAuthRequestInterceptor(username, password);
}
}
以上的範例程式,是假設帳密已經在 application.properties 配置檔定義好,再透過 ${}
的語法來取用。
(五)全域配置
前面自定義的 ReqresFeignConfig 類別並沒有冠上 @Configuration
注解,原因是這些配置是專門給對應的 OpenFeign 呼叫介面所使用。
一旦冠上了 @Configuration
注解,那麼裡頭的配置就會套用在「所有的」OpenFeign 呼叫介面。
如果有一些配置可以共用的,那不妨抽出來放在另一個配置類別,使其成為全域的配置。下面是以 OkHttpClient 為例:
import okhttp3.OkHttpClient;
@Configuration
public class GlobalFeignConfig {
@Bean
public OkHttpClient client() {
return new OkHttpClient.Builder()
.callTimeout(20, TimeUnit.SECONDS)
.build();
}
}
筆者第一次使用 OpenFeign 時,不知道有這種用法,且不小心冠上了 @Configuration
注解。使得上述 BasicAuthRequestInterceptor
的 HTTP Basic 認證資訊變成全域配置,被套用在呼叫其他系統的介面上,導致存取那些 API 時被拒絕。這個案例也與讀者分享。
五、例外處理
OpenFeign 發出 request 後,若發生逾時,或 response 的 HTTP 狀態碼是 4、5 開頭,就會拋出 FeignException
。以下介紹幾種例外處理的方式。
(一)處理例外物件
我們可以從 FeignException
取出 response body、header、狀態碼,或是當初的 request 相關資料,根據需要做處理。示意如下:
@SpringBootTest
class OpenFeignTests {
// ...
@Autowired
private ReqresFeignClient client;
@Test
void testGetUserById() {
try {
SingleUserResponse user = client.getUserById(1);
printJSON(user);
} catch (FeignException e) {
int httpStatus = e.status();
String responseBody = e.contentUTF8();
// ...
}
}
}
(二)配置 Error Decoder
當 response 的 HTTP 狀態碼為 4、5 開頭時,OpenFeign 會拋出FeignException
。但它畢竟是比較通用的例外物件,故業務邏輯需自行解析裡面的資料。
我們可以配置 ErrorDecoder
,當發生錯誤時,將 response 的相關資料整理成自定義的例外物件,讓開發團隊更容易使用與理解。示意如下:
public class ReqresFeignConfig {
@Bean
public ErrorDecoder errorDecoder() {
return (methodKey, response) -> {
int httpStatus = response.status();
Request request = response.request();
FeignException feignException = FeignException.errorStatus(methodKey, response);
if (httpStatus == 400) {
return new IllegalArgumentException();
} else {
return feignException;
}
};
}
}
ErrorDecoder
是一個介面,建立元件時需實作它的 decode
方法。此處的範例直接簡化成 lambda 寫法。讀者也能另外建立 ErrorDecoder
介面的物件,將程式碼抽離出去。
我們可從 Response
物件中取出 response 和當初的 request 相關資訊,也能直接轉換成前面提到的 FeignException
。最後再做資料處理,包裝成自己的 exception。
之後發送 request 後若發生問題,OpenFeign 就會拋出這些自定義的 exception。
(三)配置 Fallback
當伺服器連線逾時,無法取得回應的時候,Fallback 讓我們可以用自定義的預設資料當成 response,使程式能繼續運行。
請建立 Fallback 元件,並實作 OpenFeign 的呼叫介面。也就是說,我們會為每個 API 方法定義預設的 response。此處僅回傳空物件。
@Component
public class ReqresFeignFallback implements ReqresFeign {
@Override
public SingleUserResponse getUserById(int userId) {
return new SingleUserResponse();
}
@Override
public CreateUserResponse createUser(CreateUserRequest request) {
return new CreateUserResponse();
}
@Override
public ListUserResponse getUsers(int page, int size) {
return new ListUserResponse();
}
}
接著將 Fallback 的類別,傳入 OpenFeign 介面的 @FeignClient
注解的 fallback
參數。
@FeignClient(
name = "reqres-feign",
url = "https://reqres.in/api",
configuration = ReqresFeignConfig.class,
fallback = ReqresFeignFallback.class)
public interface ReqresFeign {
// ...
}
接著我們得啟用「斷路器」(circuit breaker)的機制。
在電氣設備中,斷路器的用途是在電流過大時斷電,避免損壞。而在軟體領域中,則是在遠端服務不可用時,中斷對其的存取。
請在 pom.xml 檔案添加斷路器的依賴,此處採用叫做「resilience4j」的實作。這個斷路器本身也是 Spring Cloud 底下的 library。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
然後在 application.properties 檔案中添加參數。
feign.circuitbreaker.enabled=true
如此一來,Fallback 就會生效了。若讀者想要測試,可以自行將連線逾時的時間設成很小,如 1 毫秒。
public class ReqresFeignConfig {
@Bean
public OkHttpClient client() {
return new OkHttpClient.Builder()
.callTimeout(1, TimeUnit.MILLISECONDS)
.build();
}
}
本文的完成後專案,請點我。