enablefeignclients_【Spring Cloud】OpenFeign之@EnableFeignClients(1)注册默认配置

9ae002288f58101ccc5861a40fe657c1.gif

【Spring Cloud】OpenFeign之@EnableFeignClients(1)注册默认配置

OpenFeignSpring Cloud 在原有 NetflixFeign 的基础之上改造成自己的远程调用组件。

OpenFeign 是个声明式 RESTful 网络请求客户端,使得编写 Web 服务客户端更加方便和快捷,只需要使用 OpenFeign 提供的注解修饰定义网络请求的接口类,就可以使用该接口的实例发送 RESTful 风格的网络请求 。OpenFeign 还可以集成 RibbonHystrix 来提供负载均衡和网络断路器的功能。

RESTful 网络请求是指 RESTful 风格的网络请求,其中 RESTResource Representational State Transfer 的缩写,直接翻译即“资源表现层状态转移”。Resource 代表互联网资源。所谓“资源”是网络上的一个实体,或者说网上的一个具体信息。它可以是一段文本、一首歌曲、一种服务,可以使用一个 URI 指向它,每种“资源”对应一个 URI。Representational 是“表现层”意思。“资源”是一种消息实体,它可以有多种外在的表现形式,我们把“资源”具体呈现出来的形式叫作它的“表现层”。比如说文本可以用 TXT 格式进行表现,也可以使用 XML 格式、 JSON 格式和二进制格式;视频可以用 MP4 格式表 现,也可以用 AVI 格式表现。URI 只代表资源的实体,不代表它的形式 。它的具体表现形 式,应该由 HTTP 请求的头信息 AcceptContent-Type 字段指定,这两个字段是对“表现层”的描述。State Transfer 是指“状态转移”。客户端访问服务的过程中必然涉及数据和状态的转化。如果客户端想要操作服务端资源,必须通过某种手段,让服务器端资源发生“状态转移”。而这种转化是建立在表现层之上的,所以被称为 “表现层状态转移”。客户端通过使 HTTP 协议中的四个动词来实现上述操作,它们分别是:获取资源的 GET、 新建或更新资源的 POST 、更新资源的 PUT 和删除资源的 DELETE

4.1、OpenFeign简介

首先先贴上 OpenFeign 的GitHub地址。

OpenFeign 是一个声明式、模板化的 RESTful 网络请求客户端。OpenFeign 会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign 会将函数的参数值设置到这些请求模板中。

Spring Cloud 中,使用 OpenFeign 非常简单——创建一个接口,并在接口上添加一些注解即可。除了使用 SpringMVC 的注解之外,OpenFeign 也支持多种自带的注解。

使用 OpenFeignSpring 应用架构一般分为三个部分,分别为服务注中心、服务提供者和服务消费者。服务提供者向服务注册中心注册自己,然后服务消费者通过 OpenFeign 发送请求时, OpenFeign 会向服务注册中心获取关于服务提供者的信息,然后再向服务提供者发送网络请求。

9958df6f1d6ceaa046b6e01b0dc0762c.png

4.2、核心

在阅读源码的时候,可以考虑以下两点:

  • @FeignClient 注解修饰的接口类如何创建,也就是其Bean实例如何被创建到容器中。

  • 在调用 @FeignClient 对象的网络请求相关函数时,OpenFeign 是如何发送网络请求的。

1100678a46fd11043a1737890c067820.png

4.3、源码分析

4.3.1、注解:@EnableFeignClients

@EnableFeignClients 类似 @EnableEurekaServer 注解,其作用就像是 OpenFeign 的开关一样,一切 OpenFeign 的相关操作都是从它开始的。@EnableFeignClients 有几个作用,一个是引入了 FeignClientsRegistrar;二是指定扫描了 FeignClient 的包信息,就是指定了 FeignClient 接口类所在的包名;三是指定了 FeignClient 接口类的自定义配置类。

@EnableFeignClients 注解的定义如下:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients {      //下面三个函数都是为了指定需要扫描的包     String[] value() default {};     String[] basePackages() default {};     Class>[] basePackageClasses() default {};      //指定自定义feignclient的自定义配置,可以配置Decoder、Encoder、Contract等组件,FeignClientsConfiguration是默认的配置     Class>[] defaultConfiguration() default {};      Class>[] clients() default {}; }

在上述注解定义中,引入了 FeignClientsRegistrar 组件,而该类是 ImportBeanDefinitionRegistrar 的子类,用于动态注册 BeanDefinition。所以 OpenFeign 通过 FeignClientsRegistrar 来处理 @FeignClient 注解修饰的 FeignClient 接口类,将这些接口类的 BeanDefinition 注册到 Spring 容器中,这样就可以使用 @Autowired 等方式来自动装载这些 FeignClient 接口类的 Bean 实例。

FeignClientsRegistrar 的部分代码如下:

class FeignClientsRegistrar        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {    //other...    @Override    public void registerBeanDefinitions(AnnotationMetadata metadata,                                        BeanDefinitionRegistry registry) {        //从@EnableFeignClients注解的属性值来构建Feign的自定义Configuration进行注册        registerDefaultConfiguration(metadata, registry);        //扫描package,注册被@FeignClient注解修饰的接口类的bean信息        registerFeignClients(metadata, registry);    }}

如上述代码所示,FeignClientsRegistrarregisterBeanDefinitions 方法主要做了两个事情, 一是注册 @EnableFeignClients 提供的自定义配置类 中的相关Bean实例, 二是根据 @EnableFeignClients 提供的包信息扫描 @FeignClient 注解修饰的 FeignClient 接口类,然后进行 Bean 实例注册。

4.3.1.1、注册默认配置
//FeignClientsRegistrar#registerDefaultConfigurationprivate void registerDefaultConfiguration(AnnotationMetadata metadata,      BeanDefinitionRegistry registry) {    //获取@EnableFeignClients注解的属性键值对    Map<String, Object> defaultAttrs = metadata        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);    //如果配置了@EnableFeignClients的defaultConfiguration属性,则进行注册该属性对应的Configuration    //后期使用default {}    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {        String name;        //判断是否是内部类        if (metadata.hasEnclosingClass()) {            name = "default." + metadata.getEnclosingClassName();        }        else {            //name="default" + "." + @EnableFeignClients修饰的类路径            name = "default." + metadata.getClassName();        }        //注册defaultConfiguration指定的Configuration配置类        registerClientConfiguration(registry, name,                                    defaultAttrs.get("defaultConfiguration"));    }}

如上述代码所示, registerDefaultConfiguration 方法会判断 @EnableFeignClients 注解是否设置了 defaultConfiguration, 如果有,则将调用 registerClientConfiguration 方法,进行 BeanDefinitionRegistry 的注册。

//FeignClientsRegistrar#registerClientConfigurationprivate void registerClientConfiguration(BeanDefinitionRegistry registry,                                          Object name, Object configuration) {    //使用BeanDefinitionBuilder来生成BeanDefinition,并注册到registry中    BeanDefinitionBuilder builder = BeanDefinitionBuilder        .genericBeanDefinition(FeignClientSpecification.class);    //添加构造函数的参数    builder.addConstructorArgValue(name);    builder.addConstructorArgValue(configuration);    registry.registerBeanDefinition(        name + "." + FeignClientSpecification.class.getSimpleName(),        builder.getBeanDefinition());}

BeanDefinitionRegistrySpring 框架中用于动态注册 BeanDefinition 信息的接口,调用其registerBeanDefinition 方法可以将 BeanDefinition 注册到 Spring 容器中,其中 name属性就是注册 BeanDefinition 的名称,类似:default.xxx.FeignClientSpecification,这里的xxx一般为使用 @EnableFeignClients 修饰的类的全路径。

重点:FeignClientSpecification 类实现了 NamedContextFactory.Specification 接口,Spring Cloud 框架使用 NamedContextFactory 来创建一系列的运行上下文(ApplicationContext),来让对应的 Specification 在这些上下文中创建实例对象,这样使得各个子上下文中的实例对象相互独立,互不影响,可以方便地通过子上下文管理一系列不同的实例对象。

public abstract class NamedContextFactoryextends NamedContextFactory.Specification>    implements DisposableBean, ApplicationContextAware {  private final String propertySourceName;  private final String propertyName;    //每个name对应一个ApplicationContext  private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();    //每个name对应一个Specification  private Map<String, C> configurations = new ConcurrentHashMap<>();    //父ApplicationContext  private ApplicationContext parent;  private Class> defaultConfigType;  public NamedContextFactory(Class> defaultConfigType, String propertySourceName,      String propertyName) {    this.defaultConfigType = defaultConfigType;    this.propertySourceName = propertySourceName;    this.propertyName = propertyName;  }  @Override  public void setApplicationContext(ApplicationContext parent) throws BeansException {    this.parent = parent;  }    //在FeignAutoConfiguration#feignContext方法会调用  public void setConfigurations(List configurations) {    for (C client : configurations) {      this.configurations.put(client.getName(), client);    }  }  public Set<String> getContextNames() {    return new HashSet<>(this.contexts.keySet());  }    //实现了DisposableBean接口,在对象消亡时会调用destroy方法来销毁bean实例  @Override  public void destroy() {    Collection values = this.contexts.values();    for (AnnotationConfigApplicationContext context : values) {      // This can fail, but it never throws an exception (you see stack traces      // logged as WARN).      context.close();    }    this.contexts.clear();  }    //根据name获取ApplicationContext,如果不存在,则先创建一个AnnotationConfigApplicationContext上下文对象,并存储保存,否则直接返回name对应的AnnotationConfigApplicationContext上下文对象  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);  }    //创建与name对应的AnnotationConfigApplicationContext子上下文中,并在其注册对应的配置等  protected AnnotationConfigApplicationContext createContext(String name) {    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();    if (this.configurations.containsKey(name)) {      for (Class> configuration : this.configurations.get(name)          .getConfiguration()) {                //在子上下文中注册与name关联的Configuration(独立)        context.register(configuration);      }    }    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {      if (entry.getKey().startsWith("default.")) {                //在子上下文中注册默认的Configuration(共用)        for (Class> configuration : entry.getValue().getConfiguration()) {          context.register(configuration);        }      }    }        //注册PropertyPlaceholderAutoConfiguration和FeignClientsConfiguration配置类    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的parent相同,这样一些相同的bean可以通过parent的context来获取      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;  }  protected String generateDisplayName(String name) {    return this.getClass().getSimpleName() + "-" + name;  }    //从name对应的AnnotationConfigApplicationContext获取单个Bean实例信息  public  T getInstance(String name, Classtype) {    AnnotationConfigApplicationContext context = getContext(name);    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,        type).length > 0) {      return context.getBean(type);    }    return null;  }  public  ObjectProvider getLazyProvider(String name, Classtype) {    return new ClientFactoryObjectProvider<>(this, name, type);  }  public  ObjectProvider getProvider(String name, Classtype) {    AnnotationConfigApplicationContext context = getContext(name);    return context.getBeanProvider(type);  }    //  public  T getInstance(String name, Class> clazz, Class>... generics) {    ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);    return getInstance(name, type);  }  @SuppressWarnings("unchecked")  public  T getInstance(String name, ResolvableType type) {    AnnotationConfigApplicationContext context = getContext(name);    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,        type);    if (beanNames.length > 0) {      for (String beanName : beanNames) {        if (context.isTypeMatch(beanName, type)) {          return (T) context.getBean(beanName);        }      }    }    return null;  }    //从name对应的AnnotationConfigApplicationContext中获取T类型的Bean实例,由BeanName与Bean组成的K-V对象  public  Map<String, T> getInstances(String name, Classtype) {    AnnotationConfigApplicationContext context = getContext(name);    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,        type).length > 0) {      return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);    }    return null;  }  /**   * Specification with name and configuration.   */  public interface Specification {    String getName();    Class>[] getConfiguration();  }}

NamedContextFactory 有几个功能:

  • 创建 AnnotationConfigApplicationContext 子上下文(#createContext方法);

  • 在子上下文中创建并获取 Bean 实例(#createContext方);

  • 当子上下文消亡时清除其中的 Bean 实例(#destroy方法):由于 NameContextFactory 实现了DisposableBean 接口 ,当 NamedContextFactory 实例消亡时, Spring 框架会调用其 destroy 方法,清除掉自己创建的所有子上下文和自身包含的所有组件实例。

OpenFeign 中, FeignContext 继承了 NamedContextFactory ,用于存储各类 OpenFeign 的组件实例。

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {  public FeignContext() {    super(FeignClientsConfiguration.class, "feign", "feign.client.name");  }}

FeignContext 的构造方法调用父类的构造方法,传入的默认配置类defaultConfigTypeFeignClientsConfigurationpropertySourceNameFeignpropertyNamefeign.client.name

@Configuration(proxyBeanMethods = false)public class FeignClientsConfiguration {  @Autowired  private ObjectFactory messageConverters;  @Autowired(required = false)  private List parameterProcessors = new ArrayList<>();  @Autowired(required = false)  private List feignFormatterRegistrars = new ArrayList<>();  @Autowired(required = false)  private Logger logger;  @Autowired(required = false)  private SpringDataWebProperties springDataWebProperties;  @Bean  @ConditionalOnMissingBean  public Decoder feignDecoder() {    return new OptionalDecoder(        new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));  }  @Bean  @ConditionalOnMissingBean  @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")  public Encoder feignEncoder() {    return new SpringEncoder(this.messageConverters);  }  @Bean  @ConditionalOnClass(name = "org.springframework.data.domain.Pageable")  @ConditionalOnMissingBean  public Encoder feignEncoderPageable() {    PageableSpringEncoder encoder = new PageableSpringEncoder(        new SpringEncoder(this.messageConverters));    if (springDataWebProperties != null) {      encoder.setPageParameter(          springDataWebProperties.getPageable().getPageParameter());      encoder.setSizeParameter(          springDataWebProperties.getPageable().getSizeParameter());      encoder.setSortParameter(          springDataWebProperties.getSort().getSortParameter());    }    return encoder;  }  @Bean  @ConditionalOnMissingBean  public Contract feignContract(ConversionService feignConversionService) {    return new SpringMvcContract(this.parameterProcessors, feignConversionService);  }  @Bean  public FormattingConversionService feignConversionService() {    FormattingConversionService conversionService = new DefaultFormattingConversionService();    for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {      feignFormatterRegistrar.registerFormatters(conversionService);    }    return conversionService;  }  @Bean  @ConditionalOnMissingBean  public Retryer feignRetryer() {    return Retryer.NEVER_RETRY;  }  @Bean  @Scope("prototype")  @ConditionalOnMissingBean  public Feign.Builder feignBuilder(Retryer retryer) {    return Feign.builder().retryer(retryer);  }  @Bean  @ConditionalOnMissingBean(FeignLoggerFactory.class)  public FeignLoggerFactory feignLoggerFactory() {    return new DefaultFeignLoggerFactory(this.logger);  }  @Bean  @ConditionalOnClass(name = "org.springframework.data.domain.Page")  public Module pageJacksonModule() {    return new PageJacksonModule();  }  @Configuration(proxyBeanMethods = false)  @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })  protected static class HystrixFeignConfiguration {    @Bean    @Scope("prototype")    @ConditionalOnMissingBean    @ConditionalOnProperty(name = "feign.hystrix.enabled")    public Feign.Builder feignHystrixBuilder() {      return HystrixFeign.builder();    }  }}

在讲解 @EnableFeignClients 注解的时候介绍到, FeignClientsConfigurationOpenFeign 默认的配置类,它会创建一些默认的 Bean 对象,比如 EncoderDecoderContractRetryer 等。后面再介绍,在此先过。

总结:NamedContextFactory 会创建出 AnnotationConfigApplicationContext 实例,并以 name 作为标识,然后每个 AnnotationConfigApplicationContext 实例都会注册部分配置类, 从而可以给出一系列基于配置类生成的组件实例,这样就可以基于 name 来管理一系列的组件实例,为不同的 FeignClient 准备不同配置组件实例, 比如说 DecoderEncoder等。

8f07ed463c31a37844b8be6858b9529e.gif

更多分享正在路上,尽情期待...

最新:

微服务核心架构梳理

【Spring Cloud】Eureka端点&集群&安全认证

【SpringCloud】Eureka Client源码分析

Eureka Server 源码分析

服务注册与发现 - Eureka

acf5d46964c5ec51c54c4c09b15a34a1.png

8aee78beb63d35a817e31a6c2233d2c1.gif

点击下方↓↓↓
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值