博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenFeign学习(八):Spring Cloud OpenFeign的加载配置原理
阅读量:4097 次
发布时间:2019-05-25

本文共 11519 字,大约阅读时间需要 38 分钟。

说明

在上篇博文中,我介绍了Spring Cloud OpenFeign的简单用法。在本篇博文中,我将继续对Spring Cloud OpenFeign进行学习,通过源码介绍Spring Cloud是如何对OpenFeign进行集成支持,如何进行加载配置。

正文

注解的使用

在阅读源码前,我们先通过文档简单了解下Spring对OpenFeign进行了那些扩展支持:

Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.

可以看到,Spring Cloud对OpenFeign添加了Spring MVC 注解,与Spring Web相同的HttpMessageConverters,以及集成了Ribbon, Eureka 和 Spring Cloud LoadBalancer。

接下来,我们通过阅读源码来了解Spring Cloud OpenFeignhi如何进行加载配置的:

在之前的使用介绍中,我们主要用了以下两个注解:

  • 在启动类上使用@EnableFeignClients注解
@EnableDiscoveryClient@EnableFeignClients@SpringBootApplicationpublic class Feign2ClientApplication {	public static void main(String[] args) {		SpringApplication.run(Feign2ClientApplication.class, args);	}}
  • 在服务接口类上使用@FeignClient注解
@FeignClient(value = "feign2")public interface Feign2Service {}

在@EnableFeignClients注解中通过@Import注解将FeignClientsRegistrar类导入容器,并且通过该注解可以配置FeignClient的默认配置,扫描基础包等。

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})@Documented@Import({FeignClientsRegistrar.class})public @interface EnableFeignClients {    String[] value() default {};    String[] basePackages() default {};    Class
[] basePackageClasses() default {}; Class
[] defaultConfiguration() default {}; Class
[] clients() default {};}

通过@FeignClient注解来配置对应服务客户端,该注解只能作用于接口类上,也可以通过该注解对客户端进行个性化配置,客户端名称,配置类,调用url等。

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FeignClient {    @AliasFor("name")    String value() default "";    /** @deprecated */    @Deprecated    String serviceId() default "";    String contextId() default "";    @AliasFor("value")    String name() default "";    String qualifier() default "";    String url() default "";    boolean decode404() default false;    Class
[] configuration() default {}; Class
fallback() default void.class; Class
fallbackFactory() default void.class; String path() default ""; boolean primary() default true;}

通过两个注解,我们就可以在spring cloud中使用默认配置的OpenFeign了,十分方便,不需要再通过Builder手动创建。

在配置加载中,除了FeignClientsRegistrar来进行注册配置外,Spring Boot 还通过AutoConfigurationImportSelector类来扫描配置在 META-INF/spring.factories文件中的自动装配类。在Spring Cloud OpenFeign中,配置了以下类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\org.springframework.cloud.openfeign.FeignAutoConfiguration,\org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

可以看到有FeignRibbonClientAutoConfiguration, FeignAutoConfiguration等。

关于Spring Boot的自动装配原理,可以看以下博文:

https://mp.weixin.qq.com/s/RsIdbv22eh9GMEE9hvV5Wg

https://mp.weixin.qq.com/s/p3p8n2WMuukvy7eYFNzwTQ

FeignClientsRegistrar

该类实现了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware三个接口。分别实现了registerBeanDefinitions,setResourceLoader,setResourceLoader方法。

该类的调用是在ApplicationContext在refresh方法中,通过ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法进行调用。

private void loadBeanDefinitionsFromRegistrars(Map
registrars) { registrars.forEach((registrar, metadata) -> { registrar.registerBeanDefinitions(metadata, this.registry); });}

可以看到直接的方法调用的就是ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法。

registerBeanDefinitions

通过该方法进行默认配置和FeignClient的注册。

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    this.registerDefaultConfiguration(metadata, registry);    this.registerFeignClients(metadata, registry);}

方法有两个参数metadata,registry。metadata为项目的启动类,这里为Feign2ClientApplication。registry为BeanDefinitionRegistry的实现类DefaultListableBeanFactory。

通过方法名称可知,首先进行默认配置的注册,再进行FeginClient的注册。

registerDefaultConfiguration

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    // 获取启动类上@EnableFeignClients注解的属性信息    Map
defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; // 设置部分beanName 如果是内部类 则设置top level类的名称 if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } // 在注解属性中得到默认配置类进行注册 this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); }}

通过代码可以看到,先获取了启动类上的@EnableFeignClients的注解配置的属性信息,默认情况下,属性值全为空。接下来根据启动类属性设置beanName相关信息,可以看到默认的配置,bean名称以default作为前缀。最后调用registerClientConfiguration方法进行注册。

registerClientConfiguration

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {    // 通过BeanDefinitionBuilder创建FeignClientSpecification类的BeanDefinition    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);    // 设置构造参数    builder.addConstructorArgValue(name);    builder.addConstructorArgValue(configuration);    // bean注册    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());}

在该方法中,首先创建了FeignClientSpecification类的BeanDefinition构造器,该类是Spring Cloud 提供的FeignClient对应配置基础类。该类有两个参数,分别为client对应name和对应配置类的数组configuration。

class FeignClientSpecification implements Specification {    private String name;    private Class
[] configuration;}

接下来,通过addConstructorArgValue方法设置该类的构造参数值,name为之前设置的 default.启动类全类名, configuration为@EnableFeignClients注解的配置属性值。

最后。进行默认配置bean的注册,beanName为 default.启动类全类名.FeignClientSpecification。

至此,通过@EnableFeignClients注解为所有FeignClient配置的Configuration类注册完毕。

registerFeignClients

用该方法注册使用@FeignClient注解配置的Client。

该方法有些长,我们分开进行阅读了解。

首先获取了生成bean的扫描对象scanner,该对象为ClassPathScanningCandidateComponentProvider的实例对象。(这里有个知识点,在如何判断为待注册bean中,使用了beanDefinition.getMetadata().isIndependent()方法,关于isIndependent()方法的解释,详见:https://www.jianshu.com/p/107c05b29290

)。

接下来,获取@EnableFeignClients注解中配置的clients,若有配置值,则通过Set basePackages进行收集类所在包。否则通过注解属性value,basePackages,basePackageClasses的值获取要扫描的包路径。同时设置注解类型过滤器AnnotationTypeFilter,过滤具有FeignClient注解的类。

在获取扫描路经时如果没有配置属性值,则获取启动类的package根路径。这也是为什么要将@EnableFeignClients注解放到启动类的原因。

// 生成bean扫描类ClassPathScanningCandidateComponentProvider scanner = this.getScanner();scanner.setResourceLoader(this.resourceLoader);Map
attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 生成注解类过滤器AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);// 获取@EnableFeignClients配置的clients类Class
[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));Object basePackages;// 若clients值存在 进行收集包路径if (clients != null && clients.length != 0) { final Set
clientClasses = new HashSet(); basePackages = new HashSet(); Class[] var9 = clients; int var10 = clients.length; for(int var11 = 0; var11 < var10; ++var11) { Class
clazz = var9[var11]; ((Set)basePackages).add(ClassUtils.getPackageName(clazz)); // 收集配置的client类 作为过滤的依据 clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { protected boolean match(ClassMetadata metadata) { // 将内部类的类名转换为java语言规范定义的格式 String cleaned = metadata.getClassName().replaceAll("\\$", "."); // 根据配置进行过滤 return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));} else { // 在该方法中 根据@EnableFeignClients配置的value,basePackages,basePackageClasses属性值设置基础扫描路径 // 若无配置值,则将启动类的包根路径设置为扫描路径 scanner.addIncludeFilter(annotationTypeFilter); basePackages = this.getBasePackages(metadata);}

在获取到扫描基础包和配置的client类后,进行扫描获取FeignClient的BeanDefinition进行注册。

在注册时,对@FeignClient注解是否作用在接口类进行校验,再获取配置的属性值,分别注册对应的配置类和client。

Iterator var17 = ((Set)basePackages).iterator();while(var17.hasNext()) {    String basePackage = (String)var17.next();    // 扫描获取FeignClient的BeanDefinition    Set
candidateComponents = scanner.findCandidateComponents(basePackage); Iterator var21 = candidateComponents.iterator(); while(var21.hasNext()) { BeanDefinition candidateComponent = (BeanDefinition)var21.next(); if (candidateComponent instanceof AnnotatedBeanDefinition) { 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()); String name = this.getClientName(attributes); // 注册配置的configuration // beanName为 clientName.FeignClientSpecification this.registerClientConfiguration(registry, name, attributes.get("configuration")); // 注册client 实际为注册对应FeignClientFactoryBean this.registerFeignClient(registry, annotationMetadata, attributes); } }}

registerFeignClient

通过该方法注册FeignClient对应的FeignClientFactoryBean。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map
attributes) { // 获取注解作用类的类名作为beanName String className = annotationMetadata.getClassName(); // 生成FeignClientFactoryBean的BeanDefinition构造器 BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class) // 对配置的hyxtrix降级配置进行校验 this.validate(attributes); definition.addPropertyValue("url", this.getUrl(attributes)); definition.addPropertyValue("path", this.getPath(attributes)); String name = this.getName(attributes); definition.addPropertyValue("name", name); // 获取配置的contextId 若没有配置 则使用clientName String contextId = this.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")); // 设置自动注入类型为AUTOWIRE_BY_TYPE definition.setAutowireMode(2); // 设置bean别名 String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); beanDefinition.setPrimary(primary); String qualifier = this.getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias}); // 注册FeignClientFactoryBean BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

至此,通过FeignClientsRegistrar进行FeignClient的加载注册源码已经阅读完毕,可以看到Spring Cloud OpenFeign通过@EnableFeignClients和@FeignClient两个注解实现FeignClient的配置与加载,在注册client时,实际注册的是接口类对应的FeignClientFactoryBean。

上面提到Spring Cloud在spring.factories文件中配置了自动装配类,接下来我将继续通过源码介绍这些类是如何进行配置OpenFeign的。

转载地址:http://wceii.baihongyu.com/

你可能感兴趣的文章
JavaBean
查看>>
Spring简介
查看>>
Spring搭建环境与实例化容器
查看>>
Spring依赖注入的方式
查看>>
spring集成jdbc
查看>>
组件扫描
查看>>
spring进行简单的查询
查看>>
SpringBoot发送邮箱
查看>>
Servlet过滤器
查看>>
Servlet监听器
查看>>
文件上传下载
查看>>
Spring_AOP
查看>>
SpringMVC简单介绍与REST风格的URL
查看>>
Spring事务详解
查看>>
springMvc文件上传下载
查看>>
数据校验框架
查看>>
访问数据模块
查看>>
JDBC基础
查看>>
SpringMVC视图解析
查看>>
AJAX
查看>>