【Spring Cloud】OpenFeign之@EnableFeignClients(1)注册默认配置
OpenFeign 是 Spring Cloud 在原有 Netflix 的 Feign 的基础之上改造成自己的远程调用组件。
OpenFeign 是个声明式 RESTful 网络请求客户端,使得编写 Web 服务客户端更加方便和快捷,只需要使用 OpenFeign 提供的注解修饰定义网络请求的接口类,就可以使用该接口的实例发送 RESTful 风格的网络请求 。OpenFeign 还可以集成 Ribbon 和 Hystrix 来提供负载均衡和网络断路器的功能。
RESTful 网络请求是指 RESTful 风格的网络请求,其中 REST 是 Resource Representational State Transfer 的缩写,直接翻译即“资源表现层状态转移”。Resource 代表互联网资源。所谓“资源”是网络上的一个实体,或者说网上的一个具体信息。它可以是一段文本、一首歌曲、一种服务,可以使用一个 URI 指向它,每种“资源”对应一个 URI。Representational 是“表现层”意思。“资源”是一种消息实体,它可以有多种外在的表现形式,我们把“资源”具体呈现出来的形式叫作它的“表现层”。比如说文本可以用 TXT 格式进行表现,也可以使用 XML 格式、 JSON 格式和二进制格式;视频可以用 MP4 格式表 现,也可以用 AVI 格式表现。URI 只代表资源的实体,不代表它的形式 。它的具体表现形 式,应该由 HTTP 请求的头信息 Accept 和 Content-Type 字段指定,这两个字段是对“表现层”的描述。State Transfer 是指“状态转移”。客户端访问服务的过程中必然涉及数据和状态的转化。如果客户端想要操作服务端资源,必须通过某种手段,让服务器端资源发生“状态转移”。而这种转化是建立在表现层之上的,所以被称为 “表现层状态转移”。客户端通过使 HTTP 协议中的四个动词来实现上述操作,它们分别是:获取资源的 GET、 新建或更新资源的 POST 、更新资源的 PUT 和删除资源的 DELETE。
4.1、OpenFeign简介
首先先贴上 OpenFeign 的GitHub地址。
OpenFeign 是一个声明式、模板化的 RESTful 网络请求客户端。OpenFeign 会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign 会将函数的参数值设置到这些请求模板中。
在 Spring Cloud 中,使用 OpenFeign 非常简单——创建一个接口,并在接口上添加一些注解即可。除了使用 SpringMVC 的注解之外,OpenFeign 也支持多种自带的注解。
使用 OpenFeign 的 Spring 应用架构一般分为三个部分,分别为服务注中心、服务提供者和服务消费者。服务提供者向服务注册中心注册自己,然后服务消费者通过 OpenFeign 发送请求时, OpenFeign 会向服务注册中心获取关于服务提供者的信息,然后再向服务提供者发送网络请求。
4.2、核心
在阅读源码的时候,可以考虑以下两点:
被 @FeignClient 注解修饰的接口类如何创建,也就是其Bean实例如何被创建到容器中。
在调用 @FeignClient 对象的网络请求相关函数时,OpenFeign 是如何发送网络请求的。
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); }}
如上述代码所示,FeignClientsRegistrar 的 registerBeanDefinitions
方法主要做了两个事情, 一是注册 @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());}
BeanDefinitionRegistry 是 Spring 框架中用于动态注册 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 的构造方法调用父类的构造方法,传入的默认配置类defaultConfigType
为 FeignClientsConfiguration,propertySourceName
为Feign
,propertyName
为feign.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 注解的时候介绍到, FeignClientsConfiguration 是 OpenFeign 默认的配置类,它会创建一些默认的 Bean 对象,比如 Encoder、Decoder、Contract、Retryer 等。后面再介绍,在此先过。
总结:NamedContextFactory 会创建出 AnnotationConfigApplicationContext 实例,并以 name 作为标识,然后每个 AnnotationConfigApplicationContext 实例都会注册部分配置类, 从而可以给出一系列基于配置类生成的组件实例,这样就可以基于 name 来管理一系列的组件实例,为不同的 FeignClient 准备不同配置组件实例, 比如说 Decoder、 Encoder等。
更多分享正在路上,尽情期待...
最新:
微服务核心架构梳理
【Spring Cloud】Eureka端点&集群&安全认证
【SpringCloud】Eureka Client源码分析
Eureka Server 源码分析
服务注册与发现 - Eureka
点击下方↓↓↓