Feign如何实现局部配置优先于全局默认配置

4 篇文章 0 订阅
  1. Feign如何实现局部配置优先于全局默认配置?

回答这个问题之前,首先需要介绍一下Feign的使用和实现过程。

基于openFeign的版本如下

version=2.1.1.RELEASE
groupId=org.springframework.cloud
artifactId=spring-cloud-openfeign-core

 

我们在启动类上标注@EnableFeignClients注解,加上全局的默认配置实现,如下图。

 

之后呢在调用远程方法的接口上标上注解FeignClient,如下图。

 

那么FeignCustomizedConfiguration2能覆盖全局默认FeignCustomizedConfiguration里面的组件对象吗?这两个类的内容一致,现给出FeignCustomizedConfiguration2类的代码

public class FeignCustomizedConfiguration2 {

    @Bean
    public Decoder feignDecoder2() {
        return new FeignResultDecoder2();
    }

    @Bean
    public RequestInterceptor headerInterceptor2() {
        return requestTemplate -> {
            ServletRequestAttributes attributes =    (ServletRequestAttributes)RequestContextHolder
                    .getRequestAttributes();
            if (attributes == null) {
                return;
            }
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader(TOKEN);
            if (!StringUtils.isEmpty(token)) {
                requestTemplate.header(TOKEN, token);
            }
            //requestTemplate.header("Host", "61.144.184.44");
            //注意压缩的问题
            Enumeration<String> enumeration = request.getHeaderNames();
            while (enumeration.hasMoreElements()) {
                String name = enumeration.nextElement();
                requestTemplate.header(name, request.getHeader(name));
            }

        };
    }
}

 

好,那么来分析一下启动流程,

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}

 

其中FeignClientsRegistrar是关键,是通过自定义注解扫描相关类的实现

看一下其中的关键源码

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);
   registerFeignClients(metadata, registry);
}

private void registerDefaultConfiguration(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   Map<String, Object> defaultAttrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

   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"));
   }
}


 

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());
}

将defaultConfiguration属性值FeignCustomizedConfiguration扫描进容器。

将FeignClient注解的接口及注解属性注册为FeignClientFactoryBean类,该对象是Feign实现动态代理的枢纽,如下。

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);
}

关注上面无论默认的Config还是局部的Config都被封装成FeignClientSpecification,并且默认的配置以default开头,其它局部的以FeignClient注解的value或name开头命名。

然后所有的FeignClientSpecification又都被设置到FeignContext中去,如下图。

public void setConfigurations(List<C> configurations) {
   for (C client : configurations) {
      this.configurations.put(client.getName(), client);
   }
}

以name为key,本记录为值。

而封装成FeignContext的信息是在FeignClientFactoryBean中使用的,代码如下

不同配置的生效边界也是在feign方法中体现出的,FeignClientFactoryBean里面记录了contextId,其用途是使用contextId从FeignContext中获取配置信息。

protected <T> T get(FeignContext context, Class<T> type) {
   T instance = context.getInstance(this.contextId, type);
   if (instance == null) {
      throw new IllegalStateException(
            "No bean found of type " + type + " for " + this.contextId);
   }
   return instance;
}



public <T> T getInstance(String name, Class<T> type) {
   AnnotationConfigApplicationContext context = getContext(name);
   try {
      return context.getBean(type);
   }
   catch (NoSuchBeanDefinitionException e) {
      // ignore
   }
   return null;
}



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);
}

而contexts类型为ConcurrentHashMap,典型的缓存用途。键为contextId,值为AnnotationConfigApplicationContext,这样在顺理成章地使用通过类型来获取实例对象。那么真相就在如何根据name(也即contextId)建立容器上下文。

protected AnnotationConfigApplicationContext createContext(String name) {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

//首先将直接能get到的config注册到刚建立的context中。
   if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name)
            .getConfiguration()) {
         context.register(configuration);
      }
   }

//其次将以default.开头命名的config注册到刚建立的context中。

   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)));
   if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      // jdk11 issue
      // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
      context.setClassLoader(this.parent.getClassLoader());
   }
   context.setDisplayName(generateDisplayName(name));
   context.refresh();
   return context;
}

首先将直接能get到的config注册到刚建立的context中。

其次将以default.开头命名的config注册到刚建立的context中。

然后刷新,将每个config里定义的Bean注册到局部context中。

所以全局默认和局部定义的组件对象,如相同接口,重复定义,会导致无法找到对应的对象。当前2.1.1版本也确实存在这个问题。

但是属性配置是可以覆盖的,默认的优先级是 局部文件配置>全局默认文件配置>局部注解配置>全局默认注解配置。当然顺序可以修改。

覆盖以下注解的设置

@FeignClient(
        value="RightServiceKey",
        url = "http://127.0.0.1:8080/iHybrid-H_Performance_API",
        path="/dictitonary"
        //,
        ,configuration = FeignCustomizedConfiguration2.class
)
public interface RightService {

}



public class FeignCustomizedConfiguration2 {

    @Bean
    public Decoder feignDecoder2() {
        return new FeignResultDecoder2();
    }

}


有代码为证

 

所以回答提出的问题:Feign如何实现局部配置优先于全局默认配置?

  1. 局部配置优先于全局默认配置,是有条件的,在配置文件配置的完全没有问题。
  2. 但是通过注解,因为当前版本实现的问题,是无法支持局部配置的组件对象覆盖默认的组件对象。其实质就是两个config里面的定义的Bean都被扫描到局部容器中了。再通过类型获取单一的组件对象时就出现了问题。
  3. 先将默认的属性值进行设入,然后根据contextId获取私有的context,从私有的容器中根据类型获取局部范围的组件。
  4. 配置文件里面的属性,也是默认先设入,然后在设入局部的属性,如果是类全称,属性是自定义对象,则需要将其实例化。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值