• @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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这是我工作中定义的一个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);
    				}
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    通过上面代码得出以下结论, 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());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过上面这两段代码得知,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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    这段是注册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));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    我们主要看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);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在看下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();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    要回答刚开始提出的那个疑问,关键代码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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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注解,那这个配置类就是一个全局配置类。

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    wpf DataGridComboBoxColumn 如何显示一个多列的下拉框?_成就一亿技术人!
    初识链表(7.25)
    C++之结构体以及通讯录管理系统
    【Vue】vue-cli一站式搭建SPA项目
    jmeter如何进行一个简单的测试(超级详细,有图有文字,闭着眼都能成功)
    基于Vue+SpringBoot的无代码动态表单系统 开源项目
    python---匿名函数应用
    MySQL基本语句
    一同走进Linux的“基操”世界
    【AGC】如何快速部署Serverless Url缩短模板
  • 原文地址:https://blog.csdn.net/m0_55070913/article/details/126070521