核心概念
Feign是Netflix开源的声明式HTTP客户端,通过接口 + 注解的方式简化服务调用,自动集成Ribbon负载均衡和Hystrix熔断降级。SpringCloud对Feign进行了增强,推出OpenFeign,支持SpringMVC注解。
传统调用 vs Feign调用
// 传统RestTemplate调用
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
String url = "http://user-service/api/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
public User createUser(User user) {
String url = "http://user-service/api/users";
return restTemplate.postForObject(url, user, User.class);
}
}
// Feign声明式调用
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable("id") Long id);
@PostMapping("/api/users")
User createUser(@RequestBody User user);
}
@Service
public class OrderService {
@Autowired
private UserServiceClient userServiceClient;
public User getUser(Long userId) {
return userServiceClient.getUser(userId); // 像调用本地方法一样
}
}
核心工作原理
整体流程
1. 启动时扫描@FeignClient注解
↓
2. 为每个FeignClient创建动态代理对象
↓
3. 调用接口方法时,进入代理逻辑
↓
4. 解析方法上的注解(@GetMapping等)
↓
5. 构造HTTP请求(RequestTemplate)
↓
6. 通过Ribbon选择服务实例(负载均衡)
↓
7. 发送HTTP请求(默认使用HttpURLConnection)
↓
8. 解析响应,转换为返回值类型
↓
9. 返回结果给调用方
核心组件解析
1. @FeignClient注解处理
@FeignClient(
name = "user-service", // 服务名(必填)
path = "/api", // 统一路径前缀
url = "", // 直接指定URL(不走服务发现)
fallback = UserServiceFallback.class, // 降级处理类
fallbackFactory = UserServiceFallbackFactory.class, // 降级工厂
configuration = FeignConfig.class // 自定义配置
)
public interface UserServiceClient {
// ...
}
源码处理流程:
// FeignClientsRegistrar 扫描并注册FeignClient
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 1. 扫描@FeignClient注解的接口
registerFeignClients(metadata, registry);
}
private void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
// 2. 为每个FeignClient注册BeanDefinition
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
2. 动态代理创建
// Feign.Builder 构建Feign客户端
public abstract class Feign {
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public <T> T newInstance(Target<T> target) {
// 解析接口方法,构建方法处理器映射
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 创建JDK动态代理
InvocationHandler handler = factory.create(target, nameToHandler);
T proxy = (T) Proxy.newProxyInstance(
target.type().getClassLoader(),
new Class<?>[]{target.type()},
handler
);
return proxy;
}
}
// FeignInvocationHandler 代理调用处理
public class FeignInvocationHandler implements InvocationHandler {
private final Map<Method, MethodHandler> dispatch;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取方法对应的处理器
MethodHandler handler = dispatch.get(method);
// 2. 执行方法调用
return handler.invoke(args);
}
}
3. 请求构造与发送
// SynchronousMethodHandler 同步方法处理器
final class SynchronousMethodHandler implements MethodHandler {
private final MethodMetadata metadata; // 方法元数据
private final Target<?> target; // 目标服务
private final Client client; // HTTP客户端
@Override
public Object invoke(Object[] argv) throws Throwable {
// 1. 根据方法元数据和参数构造请求
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 2. 应用拦截器(如添加Header)
template = targetRequest(template);
// 3. 执行请求(含重试逻辑)
return executeAndDecode(template);
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 构造Request对象
Request request = targetRequest(template);
// 发送HTTP请求
Response response = client.execute(request, options);
// 解码响应
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decoder.decode(response, metadata.returnType());
}
}
// 处理异常
throw errorDecoder.decode(methodKey, response);
}
}
// RequestTemplate 请求模板
public final class RequestTemplate {
private String method; // HTTP方法
private StringBuilder url; // URL
private Map<String, Collection<String>> headers; // 请求头
private Body body; // 请求体
public Request request() {
return Request.create(
method,
url.toString(),
headers,
body,
charset
);
}
}
4. 集成Ribbon负载均衡
// LoadBalancerFeignClient Ribbon集成
public class LoadBalancerFeignClient implements Client {
private final Client delegate; // 委托的Client
private final IClientConfig config;
@Override
public Response execute(Request request, Request.Options options) throws IOException {
URI uri = URI.create(request.url());
String serviceId = uri.getHost(); // 服务名
// 使用Ribbon选择实例
ServiceInstance instance = loadBalancer.choose(serviceId);
if (instance == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 替换URL为实际实例地址
String reconstructedUrl = loadBalancer.reconstructURI(instance, uri).toString();
Request newRequest = Request.create(
request.httpMethod(),
reconstructedUrl,
request.headers(),
request.body(),
request.charset()
);
// 委托给实际Client执行
return delegate.execute(newRequest, options);
}
}
核心配置
1. 日志配置
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
// NONE: 不记录
// BASIC: 记录请求方法、URL、响应状态码、执行时间
// HEADERS: BASIC + 请求和响应头
// FULL: HEADERS + 请求和响应体
}
}
logging:
level:
com.example.client.UserServiceClient: DEBUG # 开启Feign日志
日志输出示例:
[UserServiceClient#getUser] ---> GET http://user-service/api/users/1 HTTP/1.1
[UserServiceClient#getUser] Content-Length: 0
[UserServiceClient#getUser] ---> END HTTP (0-byte body)
[UserServiceClient#getUser] <--- HTTP/1.1 200 (125ms)
[UserServiceClient#getUser] content-type: application/json
[UserServiceClient#getUser] {"id":1,"name":"张三"}
[UserServiceClient#getUser] <--- END HTTP (26-byte body)
2. 超时配置
feign:
client:
config:
default: # 全局配置
connectTimeout: 5000 # 连接超时
readTimeout: 10000 # 读取超时
user-service: # 针对特定服务
connectTimeout: 3000
readTimeout: 5000
3. 重试配置
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
// 最大重试次数5次,初始间隔100ms,最大间隔1s
return new Retryer.Default(100, 1000, 5);
// 禁用重试
// return Retryer.NEVER_RETRY;
}
}
4. 拦截器配置
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 添加统一Header
template.header("Authorization", "Bearer " + getToken());
template.header("X-Request-Id", UUID.randomUUID().toString());
// 添加查询参数
template.query("timestamp", String.valueOf(System.currentTimeMillis()));
}
private String getToken() {
// 从ThreadLocal或SecurityContext获取Token
return "your-access-token";
}
}
5. 自定义HTTP客户端
<!-- 使用OkHttp替代HttpURLConnection -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
feign:
okhttp:
enabled: true # 启用OkHttp
httpclient:
enabled: false # 禁用Apache HttpClient
@Configuration
public class FeignOkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
.build();
}
}
降级处理
1. Fallback方式
// 降级实现类
@Component
public class UserServiceFallback implements UserServiceClient {
@Override
public User getUser(Long id) {
return new User(id, "默认用户", "fallback@example.com");
}
@Override
public User createUser(User user) {
throw new RuntimeException("服务暂时不可用,请稍后重试");
}
}
// FeignClient配置
@FeignClient(
name = "user-service",
fallback = UserServiceFallback.class
)
public interface UserServiceClient {
// ...
}
2. FallbackFactory方式(推荐)
// 降级工厂(可以获取异常信息)
@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceClient> {
@Override
public UserServiceClient create(Throwable cause) {
return new UserServiceClient() {
@Override
public User getUser(Long id) {
log.error("调用user-service失败: {}", cause.getMessage());
return new User(id, "降级用户", "fallback@example.com");
}
@Override
public User createUser(User user) {
log.error("创建用户失败: {}", cause.getMessage());
if (cause instanceof FeignException.ServiceUnavailable) {
throw new BusinessException("用户服务不可用");
}
throw new BusinessException("创建用户失败");
}
};
}
}
// FeignClient配置
@FeignClient(
name = "user-service",
fallbackFactory = UserServiceFallbackFactory.class
)
public interface UserServiceClient {
// ...
}
性能优化
1. 连接池优化
@Configuration
public class FeignConfig {
@Bean
public Client feignClient() {
// 使用Apache HttpClient连接池
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(200); // 最大连接数
connectionManager.setDefaultMaxPerRoute(50); // 每个路由最大连接数
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.build();
return new ApacheHttpClient(httpClient);
}
}
2. 请求压缩
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json # 压缩类型
min-request-size: 2048 # 最小压缩大小
response:
enabled: true
3. HTTP/2支持
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-http2</artifactId>
</dependency>
feign:
http2:
enabled: true
常见问题
1. Feign首次调用慢
原因:懒加载,首次调用时才初始化
解决:开启饥饿加载
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 10000
lazy-init: false # 禁用懒加载
2. 接口返回值解析失败
@Configuration
public class FeignConfig {
@Bean
public Decoder feignDecoder() {
// 使用Jackson解码
return new SpringDecoder(messageConverters);
}
@Bean
public Encoder feignEncoder() {
// 使用Jackson编码
return new SpringEncoder(messageConverters);
}
}
3. 传递请求头
@Component
public class FeignHeaderInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 传递原始请求的Header
String token = request.getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
}
}
}
面试总结
Feign工作原理核心流程:
- 启动扫描:通过
@EnableFeignClients扫描@FeignClient注解的接口 - 动态代理:为每个FeignClient接口创建JDK动态代理对象
- 方法解析:解析接口方法上的SpringMVC注解(@GetMapping等)
- 请求构造:根据方法参数和注解构造
RequestTemplate - 负载均衡:通过Ribbon从Eureka获取服务实例列表并选择一个
- HTTP调用:使用HTTP客户端(默认HttpURLConnection)发送请求
- 响应解析:解码响应体,转换为方法返回值类型
- 降级处理:调用失败时执行Fallback逻辑
核心组件:
FeignInvocationHandler:代理调用入口SynchronousMethodHandler:同步方法处理器RequestTemplate:请求模板LoadBalancerFeignClient:Ribbon负载均衡集成
优势:声明式调用、自动负载均衡、集成降级、易于测试 注意:需要配置超时、重试、连接池参数避免性能问题