1、我们定义Feign Client时候,可以通过configuration参数指定一个配置类,那么指定的这个配置入口类上面是否需要添加 @Configuration 注解呢?
@FeignClient(name = "OrderServiceClient", contextId = "OrderServiceClient", url = "${order-service.baseUrl}",
fallbackFactory = OrderServiceClientFallbackFactory.class, configuration = OrderServiceClientConfiguration.class)
public interface OrderServiceClient {
/**
* 查询订单sku信息
*/
@PostMapping("/v1/order/skus")
OrderSkuResponse queryOrderSku(@NotNull @RequestBody OrderSkuRequest request);
}
@Configuration
public class OrderServiceClientConfiguration {
@Bean
public OrderServiceInterceptor orderServiceInterceptor() {
return new OrderServiceInterceptor();
}
}
这是我工作中定义的一个Feign 接口, 然后配置了configuration参数,然后在OrderServiceClientConfiguration 配置类中注入RequestInterceptor ,一开始OrderServiceClientConfiguration 添加了 @Configuration 注解。 发现其它的Feign Client调用也会执行OrderServiceInterceptor中的代码 。废话不多说,直接看下这块源代码便知分晓。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 有些代码省略 .....
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;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 这段代码可以去看看,优先级 contextId > name > value
String name = getClientName(attributes);
// configuration配置参数相关代码
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 这里面注入FeignClient的BeanDefinition
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
通过上面代码得出以下结论, FeignClient 里面的configuration参数指定的配置类,不管加不加@Configuration注解 ,spring都会注入FeignClientSpecification类型的BeanDefinition,
如果加了@Configuration注解,spring还会注入OrderServiceClientConfiguration类型 BeanDefinition 。
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
通过上面这两段代码得知,configuration 指定的配置类最终会创建相应FeignClientSpecification BeanDefinition 托管在spring容器中。 FeignClientSpecification 里面有两个属性 name、configuration
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
这段是注册FeginClient BeanDefinition相关代码,注入的是FeignClientFactoryBean类型,看名字就知道它是一个FactoryBean 类型,我们直接看getObject() 中的方法去。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
// 这段代码不细说了,这个就通过spring.factories 进来的
FeignContext context = applicationContext.getBean(FeignContext.class);
// 我们主要看这段代码
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
我们主要看feign(context)这段代码逻辑,其它的一些细节就不细说了。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
// 1、看这段代码
configureFeign(context, builder);
return builder;
}
// 2、这个方法就是处理 configuration配置参数
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
在看下configureFeign(context, builder) -> 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());
}
if (decode404) {
builder.decode404();
}
}
要回答刚开始提出的那个疑问,关键代码context.getInstances(
this.contextId, RequestInterceptor.class),我们只要把这段代码逻辑搞清楚就知道答案了。
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;
}
public static <T> Map<String, T> beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type)
throws BeansException {
Assert.notNull(lbf, "ListableBeanFactory must not be null");
Map<String, T> result = new LinkedHashMap<>(4);
result.putAll(lbf.getBeansOfType(type));
if (lbf instanceof HierarchicalBeanFactory) {
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
// 看这段代码,如果parent父容器不为空
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
// 关键代码在这里...... ,注意看这里
// 这边会取parent容器 type类型 Bean实列
Map<String, T> parentResult = beansOfTypeIncludingAncestors(
(ListableBeanFactory) hbf.getParentBeanFactory(), type);
parentResult.forEach((beanName, beanInstance) -> {
if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) {
result.put(beanName, beanInstance);
}
});
}
}
return result;
}
1、每一个Feign Client都有一个对应spring启动上下文容器,这里我们将其称做Feign Client容器,便于区分。
2、Feign Client容器有parent属性,这个parent 就是启动应用服务的spring容器,这里将其称为父容器。
3、从上面几段代码得知,FeignClient 先从对应的子容器中找RequestInterceptor Bean对象,还会从对应parent容器中找RequestInterceptor Bean对象。
如果我们在OrderServiceClientConfiguration 加上@Configuration注解,这个类会启动应用程序的spring容器(也就是FeignClient parent容器了)扫描到,这个配置类里面@Bean也会收集。
结论:@FeignClient configuration 指定的配置类不要加 @Configuration,如果加了@Configuration注解,那这个配置类就是一个全局配置类。