Springboot中的yml为单个的服务配置Feign不生效.md

一、问题

我们的服务采用OpenFeign作为服务间的调用组件,业务服务调用存储服务某个接口时,报错 timeout executing 我们使用的是全局默认配置,没有为每个client 配置单独的configuration ,查看了一下我们的配置如下:

yml 复制代码
# feign 相关配置
feign:
  sentinel:
    enabled: true
  okhttp:
    enabled: true
  httpclient:
    enabled: false
  client:
    config:
      default:
        connectTimeout: 120000
        readTimeout: 120000

该接口执行时间大约5分钟,所以需要给该服务单独配置重新配置修改如下所示:

yml 复制代码
feign:
  sentinel:
    enabled: true
  okhttp:
    enabled: true
  httpclient:
    enabled: false
  client:
    config:
      # 大文件解压操作有些耗时设置成10分钟
      micro-storage-biz:
        connectTimeout: 120000
        readTimeout: 600000
      default:
        connectTimeout: 120000
        readTimeout: 120000

这样修改之后并没有生效,我的 FeignClient 定义如下:

java 复制代码
@FeignClient(contextId = "remoteFileService", value = "micro-storage-biz")
public interface RemoteFileService {

二、解决方案

通过试错之后,修改了生效的配置

yml 复制代码
feign:
  sentinel:
    enabled: true
  okhttp:
    enabled: true
  httpclient:
    enabled: false
  client:
    config:
      # 大文件解压操作有些耗时设置成10分钟
      remoteFileService:
        connectTimeout: 120000
        readTimeout: 600000
      default:
        connectTimeout: 120000
        readTimeout: 120000

这个配置和上述配置的区别在于,将定义的 FeignClient 中的value 换成了 contextId。

先说结论,如果你的 FeignClient contextId 的值不为空,yml中必须配置值contextId 对应的值,如果只是配置了name 或者value ,在yml中配置对应的值即可

三、FeignClient 注解属性分析

  1. name/value:定义当前客户端Client的名称,用于指定注册中心中的服务名(即调用的服务)。也可以用于日志分组等。通常推荐设置为服务注册名。
  2. contextId:Spring Bean 的上下文 ID(当存在多个同名 name 时用于区分 Bean 名称)。从 Spring Cloud 2020+ 强制唯一,推荐设置
  3. url:直接指定远程服务的 URL,会绕过服务注册中心。用于测试或特殊调用。支持 ${} 占位符
  4. path:所有方法的统一前缀路径,比如设置 /api/v1,所有方法都会加上这个前缀
  5. fallback:指定一个实现了接口的类,在远程服务调用失败时提供降级逻辑(需配合 Hystrix 或 Sentinel)
  6. fallbackFactory:类似于 fallback,但可以获取异常信息(比如:日志记录或异常判断处理)。优先级高于 fallback
  7. configuration:用于定制 Feign 客户端的配置,比如:拦截器、编码器、超时设置等,仅作用于当前接口
  8. decode404:是否将 HTTP 404 响应解码为普通响应,而不是抛异常。默认为 false
  9. primary:是否设置为 @Primary Bean,默认 true。如果存在多个相同类型的 Bean,可以设置为 false

三从源码的角度解析

如果没有设置 ContextId , 会将 配置的Name/Value 的值设置成ContextId

在 FeignClientsRegistrar 中的 registerBeanDefinitions方法下的 registerFeignClients方法下的registerFeignClient方法中的 getContextId

java 复制代码
private String getContextId(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
		String contextId = (String) attributes.get("contextId");
		if (!StringUtils.hasText(contextId)) {
			return getName(attributes);
		}

		contextId = resolve(beanFactory, contextId);
		return getName(contextId);
	}
java 复制代码
/* for testing */ String getName(Map<String, Object> attributes) {
		return getName(null, attributes);
	}

	String getName(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
		String name = (String) attributes.get("serviceId");
		if (!StringUtils.hasText(name)) {
			name = (String) attributes.get("name");
		}
		if (!StringUtils.hasText(name)) {
			name = (String) attributes.get("value");
		}
		name = resolve(beanFactory, name);
		return getName(name);
	}

然后在构造这个代理对象时,找到对应的配置 // 注册FeignClient代理对象为Spring 容器的bean

java 复制代码
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
        // 获取当前的的 contextId
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
        // 构造代理对象
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
             返回代理的bean
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}
        // 将这个代理对象注册到 Spring的容器中
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        // 如果开启动态更新的配置,将这个bean注册成动态刷新的bean
		registerOptionsBeanDefinition(registry, contextId);
	}

下面是如何获取代理类的源码:

java 复制代码
<T> T getTarget() {
        // 获取Feign的上下文
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
        // 获取代理对象
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {
			if (url != null && LOG.isWarnEnabled()) {
				LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");
			}
			else if (LOG.isDebugEnabled()) {
				LOG.debug("URL not provided. Will use LoadBalancer.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

获取具体的构造代理的对象:

java 复制代码
protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(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
        // 获取对应的配置
		configureFeign(context, builder);
        //  
		applyBuildCustomizers(context, builder);

		return builder;
	}

设置对应的配置:

java 复制代码
/protected void configureFeign(FeignContext context, Feign.Builder builder) {
        // 拿到所有的配置
		FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
				: applicationContext.getBean(FeignClientProperties.class);

		FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
		setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
   
		if (properties != null && inheritParentContext) {
            //  为当前的bean 工厂配置 readTimeoutMillis  connectTimeoutMillis followRedirects等
			if (properties.isDefaultToProperties()) {
				configureUsingConfiguration(context, builder);
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
				configureUsingProperties(properties.getConfig().get(contextId), builder);
			}
			else {
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
				configureUsingProperties(properties.getConfig().get(contextId), builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
相关推荐
林林code25 分钟前
从源码的角度解读 Nacos 是如何动态刷新配置的
spring cloud
太阳之神aboluo1 小时前
SpringCloud (4) 分布式事务
java·spring·spring cloud
麦兜*8 小时前
Spring Boot 与 Ollama 集成部署私有LLM服务 的完整避坑指南,涵盖 环境配置、模型管理、性能优化 和 安全加固
java·spring boot·后端·安全·spring cloud·性能优化
麦兜*9 小时前
国产大模型平替方案:Spring Boot通义千问API集成指南
java·spring boot·后端·python·spring cloud·系统架构·springboot
JavaArchJourney9 小时前
Spring Cloud Config 核心原理
spring cloud
程序员陆通1 天前
Spring Cloud微服务中的内存泄漏问题定位与解决方案
java·spring cloud·微服务
麦兜*1 天前
Spring Integration 整合 Web3.0网关:智能合约事件监听与Spring Integration方案
java·spring boot·后端·spring·spring cloud·web3·智能合约
天机️灵韵1 天前
开源医院信息管理系统:基于若依框架的智慧医疗解决方案
java·开发语言·spring boot·spring cloud·github·开源项目
JavaArchJourney1 天前
Spring Cloud Gateway 核心原理(含源码分析)
spring cloud