1. feign.RequestInterceptor
简介
Feign 是一个声明式 Web 服务客户端,用于简化 HTTP 请求的编写与管理。feign.RequestInterceptor
是 Feign 提供的一个接口,用于在请求发出之前对其进行拦截和修改。这在微服务架构中非常有用,比如在请求中统一添加认证头、日志追踪标识、设置自定义头等,从而提升代码的可维护性和一致性。
RequestInterceptor
接口的核心方法是:
void apply(RequestTemplate template);
该方法会在每次 Feign 发起请求之前被调用。可以通过 RequestTemplate
对象修改请求的以下内容:
- 添加或修改请求头(Headers)
- 添加或修改请求参数(Query Parameters)
- 修改请求体(Body)
- 添加日志或追踪信息
- 添加身份验证信息(如 Token、OAuth 令牌等)
2. RequestInterceptor
使用示例
step1.创建 AuthRequestInterceptor。
该拦截器是一个简单的 RequestInterceptor
实现,用于在所有请求中添加认证 Token:
import feign.RequestInterceptor;
import feign.RequestTemplate;
public class AuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 添加认证 Token 到请求头
template.header("Authorization", "Bearer your_access_token_here");
}
}
step2. 将拦截器应用到 Feign Client
通过以下两种方式将 AuthRequestInterceptor
应用到你的 Feign Client 中:
方法一:通过 @FeignClient
注解配置
import org.springframework.cloud.openfeign.FeignClient;
//注意:configuration 属性接受一个类或多个类,用于配置 Feign Client 的行为。
@FeignClient(
name = "user-service",
configuration = AuthRequestInterceptor.class
)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
方法二:通过配置类注册为 Bean
先在配置类中注册 RequestInterceptor
为 Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor authRequestInterceptor() {
return new AuthRequestInterceptor();
}
}
再在 @FeignClient
中引用配置类:
@FeignClient(
name = "user-service",
configuration = FeignConfig.class
)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
3. 一个完整的示例
3.1 场景说明
在微服务架构中,使用 Feign 作为 HTTP 客户端时,我们常常需要对请求进行统一处理。这些需求可以通过多个 RequestInterceptor
实现,并在 Feign 请求发送前依次执行。下面我们通过一个实际场景来演示如何配置多个 RequestInterceptor
并使用它们。
我们有一个名为 myWeather-service
的服务,它通过 Feign 调用 一个免费的天气服务weather-service
来获取天气信息。在调用过程中,我们需要:
- 添加认证 Token(如 JWT);
- 生成请求追踪 ID(用于链路追踪);
- 记录请求日志(便于调试与监控)。
3.2 项目结构
src/main/java
├── config
│ └── FeignConfig.java
├── interceptor
│ ├── AuthRequestInterceptor.java
│ ├── TraceRequestInterceptor.java
│ └── LoggingRequestInterceptor.java
├── client
│ └── WeatherClient.java
└── MyWeatherController.java
│
└── MyWeatherWebApplication.java
3.3 完整代码
step1. 定义多个 RequestInterceptor
AuthRequestInterceptor: 添加认证信息
package com.example.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
public class AuthRequestInterceptor implements RequestInterceptor {
private final String authToken;
public AuthRequestInterceptor(String authToken) {
this.authToken = authToken;
}
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "userDefineAuthToken-" + authToken);
}
}
TraceRequestInterceptor: 添加请求追踪 ID
package com.example.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.util.UUID;
public class TraceRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String traceId = UUID.randomUUID().toString();
template.header("X-Request-TraceId", traceId);
}
}
LoggingRequestInterceptor:记录请求日志
// LoggingInterceptor.java
package com.example.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
Request request = template.request();
// 记录请求信息
System.out.println("┌───────────────────────────────────────────────────────────────────────");
System.out.println("│ Sending request method: " + template.method());
System.out.println("│ Sending request url: " + template.url());
System.out.println("│ Sending request path: " + template.path());
System.out.println("│ requestHeaders: ");
for (String headerName : request.headers().keySet()) {
System.out.println("│ " + headerName + ": " + request.headers().get(headerName));
}
System.out.println("└───────────────────────────────────────────────────────────────────────");
}
}
step2.配置多个 RequestInterceptor
将多个拦截器注册为 Bean,并统一配置到 Feign Client 中:
package com.example.config;
import com.example.interceptor.AuthInterceptor;
import com.example.interceptor.LogginInterceptor;
import com.example.interceptor.TraceInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public AuthRequestInterceptor authRequestInterceptor() {
return new AuthRequestInterceptor("authToken");
}
@Bean
public TraceRequestInterceptor requestInterceptor() {
return new TraceRequestInterceptor();
}
/**
* Feign 默认按照 Bean 注册顺序执行 RequestInterceptor。
* 将打印日志拦截器放到最后, 确保打印日志时能拿到 Auth 和 Trace 添加的请求头信息
*/
@Bean
public LoggingRequestInterceptor loggingRequestInterceptor() {
return new LoggingRequestInterceptor();
}
}
⚠️ 注意:Spring Boot 会自动将所有 RequestInterceptor
类型的 Bean 注册到 Feign 中,如无特殊需求无需手动注入。
step3. Feign Client 使用配置
这里使用FeignClient来调用免费的天气服务:
- 天气API 数据(以天津为例),链接为:http://t.weather.sojson.com/api/weather/city/101030100
package com.example.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(
name = "weather-service",
url = "http://t.weather.sojson.com/api/weather/city"
//,configuration = FeignConfig.class //这里可选,Spring Boot 会自动将所有 RequestInterceptor 类型的 Bean 注册到 Feign 中,如无特殊需求无需手动注入
)
public interface WeatherClient {
@GetMapping("/{cityId}")
String getWeather(@PathVariable(value = "cityId") String cityId);
}
step4.MyWeatherController
@RestController
public class MyWeatherController {
@Autowired
private WeatherClient weatherClient;
@GetMapping("MyWeatherController/getWeather/{cityId}")
public String getWeather(@PathVariable(value = "cityId") String cityId) {
return weatherClient.getWeather(cityId);
}
}
step5. MyWeatherWebApplication
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.example.*"})
@ComponentScan(basePackages = {"com.example.*"})
public class MyWeatherWebApplication {
public static void main(String[] args) {
SpringApplication.run(MyWeatherWebApplication.class, args);
}
}
step6.调用feign服务
开启MyWeatherWebApplication服务,浏览器输入:http://127.0.0.1:8080/MyWeatherController/getWeather/101030100,输出日志如下:
┌───────────────────────────────────────────────────────────────────────
│ Sending request method: GET
│ Sending request url: /101030100
│ Sending request path: /101030100
│ requestHeaders:
│ Authorization: [userDefineAuthToken-authToken]
│ X-Request-TraceId: [ec74d613-8f0e-442c-89cd-4a3197ad7ea0]
└───────────────────────────────────────────────────────────────────────
拦截器执行顺序说明
Feign 默认按照 Bean 注册顺序执行 RequestInterceptor
。上面例子中,FeignConfig注册bean时,若将TraceRequestInterceptor放在LoggingRequestInterceptor之后, 打印日志时将不能拿到 Trace 添加的请求头信息:
@Configuration
public class FeignConfig {
@Bean
public AuthRequestInterceptor authRequestInterceptor() {
return new AuthRequestInterceptor("authToken");
}
/**
* Feign 默认按照 Bean 注册顺序执行 RequestInterceptor。
* 将TraceRequestInterceptor放在LoggingRequestInterceptor之后, 打印日志时将不能拿到 Trace 添加的请求头信息
*/
@Bean
public LoggingRequestInterceptor loggingRequestInterceptor() {
return new LoggingRequestInterceptor();
}
@Bean
public TraceRequestInterceptor requestInterceptor() {
return new TraceRequestInterceptor();
}
}
你可以通过 @Order
注解改变执行顺序,例如:
@Order(1)
public class AuthRequestInterceptor implements RequestInterceptor { ... }
@Order(2)
public class TraceRequestInterceptor implements RequestInterceptor { ... }
3.4 何时仍需使用@FeignClient
配置
3.4.1 默认不对@FeignClient
做配置
一般情况下,无需在 @FeignClient
中通过 configuration = FeignConfig.class
显式引用配置类。
只要 FeignConfig
正确地注册了 RequestInterceptor
等 Bean(如 AuthRequestInterceptor等
),这些 Bean 就会被 Spring 容器管理,并且 全局生效。Feign Client 在创建时会自动查找 Spring 容器中所有 RequestInterceptor
类型的 Bean,并按照注册顺序依次应用。因此,只要这些拦截器被正确注册为 Spring Bean,就不需要通过 @FeignClient(configuration = ...)
引用配置类。
3.4.2 何时仍需使用@FeignClient
配置
虽然大多数情况下不需要显式引用配置类,但在以下场景中仍可能需要使用 @FeignClient(configuration = FeignConfig.class)
:
-
自定义 Feign Client 的配置需求
- 如果某个 Feign Client 需要使用特定的配置类(比如不同的拦截器组合或覆盖默认配置),可以通过
configuration
属性指定。 - 例如,某个 Feign Client 只需要
AuthInterceptor
,而其他 Feign Client 需要AuthInterceptor + LoggingInterceptor
。
- 如果某个 Feign Client 需要使用特定的配置类(比如不同的拦截器组合或覆盖默认配置),可以通过
-
控制 Bean 加载顺序
- 如果拦截器的执行顺序非常重要,可以通过
@Order
注解或在配置类中显式控制顺序。 - 使用
@FeignClient(configuration = FeignConfig.class)
可以确保配置类中的 Bean 被优先加载。
- 如果拦截器的执行顺序非常重要,可以通过
-
避免自动配置与手动配置冲突
- 如果自动配置类和手动配置类中存在相同类型的 Bean(如多个
RequestInterceptor
实现),可能会导致冲突。此时可通过@FeignClient(configuration = ...)
显式指定。
- 如果自动配置类和手动配置类中存在相同类型的 Bean(如多个
3.4.3 总结
场景 | 是否需要 @FeignClient(configuration = FeignConfig.class) |
---|---|
FeignConfig 已通过自动配置类注册到 Spring 容器 | ❌ 不需要 |
某个 Feign Client 需要自定义拦截器组合 | ✅ 需要 |
需要控制拦截器的执行顺序 | ✅ 需要(结合 @Order 使用) |
避免自动配置与手动配置冲突 | ✅ 需要 |
实践建议
- 优先使用自动配置:如果所有 Feign Client 都需要相同的拦截器配置,直接通过自动配置类注册即可,无需每个 Feign Client 配置
configuration
。 - 确保自动配置类正确注册:检查
FeignConfig
是否已注册到 Spring 容器,并验证拦截器是否生效。 - 按需定制配置:如果某些 Feign Client 需要特殊处理,再通过
@FeignClient(configuration = ...)
显式指定。