Open Feign 源码解析三 配置体系
配置类
应用级别配置(全局)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 注册feign client的bean定义
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {}; // 默认配置全局有效
Class<?>[] clients() default {};
}
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) // 配置在启动类上
@EnableDiscoveryClient
public class FeignClientMain {
// ...
}
服务级别配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
// ...
Class<?>[] configuration() default {}; // 只对服务接口有效
// ...
}
// 配置在服务接口
@FeignClient(value = "cloud-feign-server", contextId = "order", configuration = OrderConfiguration.class)
public interface OrderService {
// ...
}
@FeignClient(value = "cloud-feign-server", contextId = "user", configuration = UserConfiguration.class)
public interface UserService {
// ...
}
配置隔离原理
一句话:通过spring子容器进行隔离,不同的feign client接口对应不同的子容器,里面有自己独立的配置
1) 注册配置类到spring父容器
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
/** ImportBeanDefinitionRegistrar的方法 */
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
/** 注册默认的配置类 */
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取EableFeignClients注解的信息
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 获取defaultConfiguration的值
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 注册全局配置的
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// ...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// 获取BeanDefinition元信息
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 判断是否是接口
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 通过元信息过去FeignClient注解中的信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册服务接口的配置类
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册一个个的FeignClient的接口转换成BeanDefinition放在Bean定义容器中交给spring
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// 注意这里不是注册配置类本身 注册的是FeignClientSpecification 但里面封装了配置类
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 注册FeignClientSpecification的bean定义
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
// 把配置类通过构造方法传入
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
}
注意不是直接注册配置类本身,而是 FeignClientSpecification 类
2) 注入配置类到FeignContext
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
// 把所有FeignClientSpecification对象注入到集合里面
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// 配置就是全局配置和局部配置类封装成的一个个FeignClientSpecification
context.setConfigurations(this.configurations);
return context;
}
}
3) 从FeignContext中获取组件
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
// ...
// 使用配置类进行配置
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
// 从spring容器获取组件
Logger.Level level = getOptional(context, Logger.Level.class);
// ...
// 从spring容器获取组件
Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
// ...
}
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, type);
}
}
4) 创建子容器加载配置
// FeignContext
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
// 传入FeignClients的官方默认配置类
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
// 带名字的上下文工厂
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType; // 传入官方默认配置类
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
// 存储子容器的Map
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// value是FeignClientSpecification对象
private Map<String, C> configurations = new ConcurrentHashMap<>();
// 父容器 通过ApplicationContextAware注入
private ApplicationContext parent;
// 默认配置类是FeignClientsConfiguration
private Class<?> defaultConfigType;
/** 把配置的List转为Map */
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
// 从spring父子容器中获取单个对象
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
// 从spring父子容器中获取多个对象
public <T> Map<String, T> getInstances(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
}
return null;
}
/** 获取context */
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
/** 创建context */
protected AnnotationConfigApplicationContext createContext(String name) {
// 每个接口创建自己的子容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册属于服务接口的配置类
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 注册应用全局的配置类
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 注册默认的配置类
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
// 父容器就是当前应用的spring容器
if (this.parent != null) {
context.setParent(this.parent);
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
}
配置类示意图
parent context type : AnnotationConfigServletWebApplicationContext 不允许bean 定义覆盖
child context type: AnnotationConfigApplicationContext 允许bean 定义覆盖
问题一:
如果同时添加了全局和服务级别的配置,那会发生什么?
1)启动报错 2)全局配置生效 3)服务级别的配置生效
答案: 2)全局配置生效
allowBeanDefinitionOverriding:true 允许Bean覆盖
配置文件
application.properties 或 application.yml
feign:
client:
defaultToProperties: false
config: # 对应FeignClientProperties类的config成员变量
default: # 全局配置默认就是default
# 日志级别
logger-level: BASIC
# 超时时间
connect-timeout: 10000
order: # 是feignClient注解的ContextId
# 日志级别
logger-level: HEADERS
# 超时时间
connect-timeout: 8000
user:
# 日志级别
logger-level: FULL
# 超时时间
connect-timeout: 6000
属性绑定Properties类
@ConfigurationProperties("feign.client") // 配置的前缀 feign.client
public class FeignClientProperties {
// 以配置文件的为准
private boolean defaultToProperties = true;
// 默认配置的名称 default
private String defaultConfig = "default";
// 可以自定义多个配置 key为配置名称
private Map<String, FeignClientConfiguration> config = new HashMap<>();
/**
* Feign client configuration.
*/
public static class FeignClientConfiguration {
private Logger.Level loggerLevel; // 日志级别
private Integer connectTimeout; // 连接超时
private Integer readTimeout; // 读取超时
private Class<Retryer> retryer; // 重试
private Class<ErrorDecoder> errorDecoder; // 错误解码器
private List<Class<RequestInterceptor>> requestInterceptors; // 拦截器
private Boolean decode404;
private Class<Decoder> decoder; // 解码器
private Class<Encoder> encoder; // 编码器
private Class<Contract> contract; // 契约
}
}
配置类和配置文件的优先级
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
// ...
// 配置 feign
protected void configureFeign(FeignContext context, Feign.Builder builder) {
// 从配置文件获取(属性绑定)
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
// 如果有配置文件有配置
if (properties.isDefaultToProperties()) {
// isDefaultToProperties默认为true 即默认以配置文件的配置为准
// 因此先通过配置类进行配置 然后通过配置文件进行配置
configureUsingConfiguration(context, builder);
// 对于配置文件而言 服务级别的配置可以覆盖默认配置
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
}
else {
// isDefaultToProperties如果设置为false 即默认以配置类的配置为准
// 因此先通过配置文件进行配置 然后通过配置类进行配置
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
configureUsingConfiguration(context, builder);
}
}
else {
// 如果配置文件没有配置则直接从配置类进行配置
configureUsingConfiguration(context, builder);
}
}
// 使用配置类进行配置
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
// 日志级别
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
// 重试器
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
// 错误编码
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
// 请求参数(连接超时 读取超时等)
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
// 拦截器
Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
if (queryMapEncoder != null) {
builder.queryMapEncoder(queryMapEncoder);
}
if (this.decode404) {
builder.decode404();
}
}
// 使用配置文件进行配置
protected void configureUsingProperties(
FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
if (config == null) {
return;
}
// 日志级别
if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}
// 请求参数(连接超时 读取超时等)
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(),
config.getReadTimeout()));
}
// 重试器
if (config.getRetryer() != null) {
Retryer retryer = getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}
// 错误编码
if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}
// 拦截器
if (config.getRequestInterceptors() != null
&& !config.getRequestInterceptors().isEmpty()) {
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
// 编码器
if (Objects.nonNull(config.getEncoder())) {
builder.encoder(getOrInstantiate(config.getEncoder()));
}
// 解码器
if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder()));
}
// 契约
if (Objects.nonNull(config.getContract())) {
builder.contract(getOrInstantiate(config.getContract()));
}
}
private <T> T getOrInstantiate(Class<T> tClass) {
try {
// 直接从spring父容器中取
return this.applicationContext.getBean(tClass);
}
catch (NoSuchBeanDefinitionException e) {
return BeanUtils.instantiateClass(tClass);
}
}
// ...
}
注意: 配置类是全局配置覆盖局部配置 而 配置文件是, 局部配置覆盖全局配置(是反过来的)
具体配置举例讲解
请求拦截器
接口:
public interface RequestInterceptor {
void apply(RequestTemplate template);
}
调用拦截器:发送请求前
作用:用于修改请求url, header, body等等
final class SynchronousMethodHandler implements MethodHandler {
Request targetRequest(RequestTemplate template) {
// 调用拦截器
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 把请求模板转换为具体的请求
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 发送请求
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
// ...
}
}
获取拦截器组件: 从配置类或配置文件
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
// 使用配置类进行配置
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
// ...
// 从spring容器获取组件
Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
// ...
}
// 使用配置文件进行配置
protected void configureUsingProperties(
FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
// ...
// 拦截器
if (config.getRequestInterceptors() != null
&& !config.getRequestInterceptors().isEmpty()) {
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
// ...
}
}
问题一:
是否需要@Component注解?
- 可以: 加了就在父容器里面
- 不加, 放在用@Bean把这些放在@EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) 这个类上 或 每个对应的FeignClient的configuration中
问题二:
拦截器是全局有效的吗?如果是,可否做到只对某个服务接口有效?
- 拦截器可以全局有效, 用@Component放在父容器中, 或者放在 @EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) 指定的配置类中 或 yml配置文件中配置
- 拦截器可以局部有效: 把对应的配置类放在FeignClient的configuration中 或 配置在yml中
问题三:
拦截器是否可以自定义顺序?
-
暂时没发现其他的@Order注解 或 Order方法, 所以不能排序
-
但是可以通过BeanDefinition解析过程来决定先后顺序, 因为requestInterceptors是LinkedHashMap