@FeignClient configuration参数配置

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 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 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 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 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 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  Map getInstances(String name, Class type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
            // 看这段代码
			return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
		}
		return null;
	}



public static  Map beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type)
			throws BeansException {

		Assert.notNull(lbf, "ListableBeanFactory must not be null");
		Map 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 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也会收集。

结论:@FeignClientconfiguration 指定的配置类不要加@Configuration,如果加了@Configuration注解,那这个配置类就是一个全局配置类。

最后

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

小编已加密:aHR0cHM6Ly9kb2NzLnFxLmNvbS9kb2MvRFVrVm9aSGxQZUVsTlkwUnc==出于安全原因,我们把网站通过base64编码了,大家可以通过base64解码把网址获取下来。

你可能感兴趣的:(面试,学习路线,阿里巴巴,spring,java,spring,boot,开发语言,hadoop)