Spring Cloud OpenFeign 整合源码分析
纯 Feign 是独立库,Spring Cloud OpenFeign 把它接入 Spring 容器:扫描接口、注册 Bean、装配组件、绑定配置。本文拆解 @EnableFeignClients → FeignClientsRegistrar → FeignClientFactoryBean 的完整链路。
入口:@EnableFeignClients
java
@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.client")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}注解定义
java
// spring-cloud-openfeign-core
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 关键:导入注册器
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {}; // 扫描包
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {}; // 默认配置类
Class<?>[] clients() default {}; // 显式指定客户端
}FeignClientsRegistrar:动态注册
实现
java
// org.springframework.cloud.openfeign.FeignClientsRegistrar
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1. 注册默认配置
registerDefaultConfiguration(metadata, registry);
// 2. 扫描 @FeignClient 接口并注册
registerFeignClients(metadata, registry);
}
}扫描与注册流程
java
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1. 获取扫描包(basePackages / basePackageClasses / value)
LinkedHashSet<String> basePackages = getBasePackages(metadata);
// 2. 扫描 @FeignClient 注解接口
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setAnnotationFilter(FeignClient.class); // 过滤 @FeignClient
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 3. 读取 @FeignClient 注解属性
AnnotationMetadata annotationMetadata = ...;
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes); // 服务名
registerClientConfiguration(registry, name, attributes.get("configuration")); // 配置类注册
// 4. 注册 FeignClientFactoryBean(工厂 Bean)
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}关键点
- 扫描的接口不注册为普通 Bean,而是注册一个
FeignClientFactoryBean - 每个客户端单独注册其 configuration 配置类(支持按服务定制)
- 支持 basePackages 指定扫描范围
FeignClientFactoryBean:工厂 Bean
实现
java
// org.springframework.cloud.openfeign.FeignClientFactoryBean
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private Class<?> type; // Feign 接口类型
private String name; // 服务名
private String url; // 直连地址(可选)
private String path; // 统一前缀
private Class<?> fallback; // 降级类
private Class<?> fallbackFactory; // 降级工厂
private Class<?>[] configuration; // 配置类
@Override
public Object getObject() {
// 创建 Feign 代理
return getTarget();
}
private <T> T getTarget() {
// 1. 构建 Feign.Builder
Feign.Builder builder = feign(properties);
// 2. 处理直连/服务发现
if (url != null && !url.startsWith("http")) {
url = "http://" + url;
}
// 3. 配置加载(可覆盖)
if (url != null && !url.isEmpty()) {
return builder.target(type, url); // 直连
} else {
return builder.target(loadBalancerClientFactory, type, name); // lb://
}
}
@Override
public Object getObjectType() {
return type;
}
}构建 Feign.Builder
java
protected Feign.Builder feign(FeignContext context) {
// 1. 从 FeignContext 取该客户端的定制配置(configuration 属性)
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// 2. 装配组件(全部可从 FeignContext 覆盖)
@SuppressWarnings("unchecked")
Feign.Builder builder = Feign.builder()
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class))
.requestInterceptors(get(context, RequestInterceptor.class));
// 3. 降级处理
if (fallbackFactory != null) {
// Sentinel/Hystrix 包装 InvocationHandlerFactory
builder.invocationHandlerFactory(new FallbackFactoryInvocationHandlerFactory(...));
}
return builder;
}FeignContext:按客户端隔离配置
子上下文机制
java
// org.springframework.cloud.openfeign.FeignContext
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
// 每个客户端一个子上下文,容器隔离
// 子上下文可覆盖默认组件(Encoder/Decoder/Contract...)
}FeignContext(父上下文)
├── order-service 子上下文:OrderClientConfig(定制 Encoder、Decoder)
├── user-service 子上下文:UserClientConfig
└── default 子上下文:全局配置配置优先级
方法参数(properties) < 子上下文(@FeignClient configuration)
< 全局默认(@EnableFeignClients defaultConfiguration)配置属性绑定
FeignClientProperties
java
// org.springframework.cloud.openfeign.FeignClientProperties
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true; // 属性配置优先级
private Map<String, FeignClientConfiguration> config = new HashMap<>();
}yaml
feign:
client:
config:
default: # 全局默认
connectTimeout: 5000
readTimeout: 10000
loggerLevel: basic
order-service: # 按服务
connectTimeout: 3000
readTimeout: 5000
requestInterceptors:
- com.example.TraceInterceptor属性 → Bean 绑定
java
// FeignClientConfiguration(配置属性类)
public class FeignClientConfiguration {
private Duration connectTimeout; // 连接超时
private Duration readTimeout; // 读取超时
private Class<Logger.Level> loggerLevel;
private Class<Retryer> retryer;
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
...
}FeignClientFactoryBean 通过 FeignClientFactory 读取属性并创建对应的 Bean。
完整注册时序
启动
│
├─ 1. @EnableFeignClients → FeignClientsRegistrar
│
├─ 2. 扫描 basePackages 下所有 @FeignClient 接口
│
├─ 3. 每个接口注册:
│ ├─ BeanDefinition:FeignClientFactoryBean(name = orderClient)
│ └─ 子上下文配置类(order-service)
│
├─ 4. Bean 实例化:
│ ├─ FeignClientFactoryBean.getObject()
│ ├─ 构建 Feign.Builder(装配 Encoder/Decoder/Contract/拦截器)
│ ├─ feign.target(OrderClient.class, "order-service")
│ └─ 返回 JDK 动态代理
│
└─ 5. 注入使用:@Autowired OrderClient → 代理对象自动配置类
java
// spring.factories / AutoConfiguration.imports
// FeignAutoConfiguration(自动配置)
public class FeignAutoConfiguration {
@Bean
public FeignContext feignContext() { ... }
@Bean
public FeignClientFactory feignClientFactory() { ... }
@Bean
public Targeter feignTargeter() {
// 返回 FeignClientFactoryBean.Targeter(直连/lb 分派)
return new DefaultTargeter();
}
// 条件装配:
@ConditionalOnClass(name = "feign.okhttp.OkHttpClient")
public OkHttpFeignConfiguration ...
@ConditionalOnClass(name = "feign.httpclient.ApacheHttpClient")
public ApacheHttpClientFeignConfiguration ...
}常见问题
- Feign 接口扫描不到? 检查 @EnableFeignClients 的 basePackages 是否覆盖接口所在包。
- 配置不生效? 属性配置需
feign.client.config.xxx层级;Java 配置需@FeignClient(configuration=...),且注意两种方式优先级。 - 为什么每个客户端有独立配置? FeignContext 子上下文机制隔离,避免不同服务定制互相影响。
- 降级不生效? 需开启 Sentinel/Hystrix(
feign.sentinel.enabled=true)且 fallback 类注册为 Spring Bean。