Spring Cloud各组件深入-openFeign(服务远程调用)

在上篇文章我们看了服务注册与发现Eureka,今天我们看下服务之间的调用组件。

各组件深入之Spring Cloud openFeign

​ 在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于HTTP RESTful的。Spring Cloud有两种服务调用方式,一种是Ribbon+RestTemplate,另一种是Feign。

​ Feign是声明性Web服务客户端。 它使编写Web服务客户端更加容易。 要使用Feign,请创建一个接口并对其进行注释。 它具有可插入注释支持,包括Feign注释和JAX-RS注释。 Feign还支持可插拔编码器和解码器。 Spring Cloud添加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的相同HttpMessageConverters。 Spring Cloud集成了Eureka和Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。 就是通过把http请求封装到了注解中。

​ Spring Cloud 的 Feign 支持中的一个核心概念是命名客户机。每个佯装的客户机都是一个组件集合的一部分,这些组件一起工作,根据需要联系一个远程服务器,这个集合有一个名称,作为一个使用@feignclient 注释的应用程序开发人员,你可以给它一个名称。Spring Cloud 使用 FeignClientsConfiguration 根据需要为每个命名客户机创建一个新的集合,作为 ApplicationContext。其中包括一个假动作。解码器,一个假装。编码器,和一个假装。合约。可以使用@feignclient 注释的 contextId 属性覆盖集合的名称。

​ Hystrix 支持熔断(fallback)的概念: 一个默认的代码路径,在熔断或出现错误时执行。要为给定的@feignclient 启用熔断,请将熔断属性设置为实现熔断的类名。您还需要将实现声明为 springbean。

/**
 * 去请求feign服务端itoken-service-admin中的服务接口
 * @Author kay三石
 * @date:2019/6/22
 */
// value 是声明的方式指向了 服务提供者
@FeignClient(value="itoken-service-admin",fallback = AdminServiceFallback.class)
public interface AdminService  extends BaseClientService {

    /**
     * 根据 ID 获取管理员
     *
     * @return
     */
    @RequestMapping(value = "v1/admins", method = RequestMethod.GET)
    public String get(
            @RequestParam(required = true, value = "userCode") String userCode
    );
}

如果需要访问制造回退触发器的原因,可以在@feignclient 中使用 fallbackFactory 属性。

// name 调用服务的名称和value等
@FeignClient(name=ServiceNameConstants.DEMOB_SERVICE,fallbackFactory = DemobServiceClientFallbackFactory.class)
public interface DemobServiceClient {
    
    @GetMapping(value = "/demob/test/getDemobById")
    DemobDTO getDemobById(@RequestParam("id")String id);
    
}   
@Component
public class DemobServiceClientFallbackFactory implements FallbackFactory<DemobServiceClient> {

    @Override
    public DemobServiceClient create(Throwable cause) {
        DemobServiceClientFallback demobServiceClientFallback = new DemobServiceClientFallback();
        demobServiceClientFallback.setCause(cause);
        return demobServiceClientFallback;
    }

}
@Slf4j
public class DemobServiceClientFallback implements DemobServiceClient {
    @Setter
    private Throwable cause;
    
    @Override
    public DemobDTO getDemobById(String id) {
        log.error("根据id获取demob信息失败",cause);
        throw new FirstException();
    }

}
注解接口Target使用说明
@RequestLine方法上定义HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表达式,可以通过在方法参数上使用@Param 自动注入
@Param方法参数定义模板变量,模板变量的值可以使用名称的方式使用模板注入解析
@Headers类上或者方法上定义头部模板变量,使用@Param 注解提供参数值的注入。如果该注解添加在接口类上,则所有的请求都会携带对应的Header信息;如果在方法上,则只会添加到对应的方法请求上
@QueryMap方法上定义一个键值对或者 pojo,参数值将会被转换成URL上的 query 字符串上
@HeaderMap方法上定义一个HeaderMap, 与 UrlTemplate 和HeaderTemplate 类型,可以使用@Param 注解提供参数值
FeignClient 的配置参数
属性名默认值作用备注
value空字符串调用服务名称,和name属性相同
serviceId空字符串服务id,作用和name属性相同已过期
name空字符串调用服务名称,和value属性相同
url空字符串全路径地址或hostname,http或https可选
decode404false配置响应状态码为404时是否应该抛出FeignExceptions
configuration{}自定义当前feign client的一些配置参考FeignClientsConfiguration
fallbackvoid.class熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。底层依赖hystrix,启动类要加上@EnableHystrix
path空字符串自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping
primarytrue
Feign原理:
  • 启动时,程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。
  • RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
  • RequestTemplate声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
  • 最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用。
Feign源码解析

参考:Spring Cloud微服务架构进阶

核心组件

在阅读OpenFeign源码时,可以沿着两条路线进行,一是FeignServiceClient这样的被@FeignClient注解修饰的接口类(后续简称为FeignClient接口类)如何创建,也就是其Bean实例是如何被创建的;二是调用FeignServiceClient对象的网络请求相关的函数时,OpenFeign是如何发送网络请求的。而OpenFeign相关的类也可以以此来进行分类,一部分是用来初始化相应的Bean实例的,一部分是用来在调用方法时发送网络请求。

下图是关于Fegin的相关的关键类图。其中比较重要的类为FeignClientFactoryBean、FeignContext和SynchronousMethodHandler。FeignClientFactoryBean是创建@FeignClient修饰的接口类Bean实例的工厂类;FeignContext是配置组件的上下文环境,保存着相关组件的不同实例,这些实例由不同的FeignConfiguration配置类构造出来;SynchronousMethodHandler是MethodHandler的子类,可以在FeignClient相应方法被调用时发送网络请求,然后再将请求响应转化为函数返回值进行输出。

cIDO0J.png

OpenFeign会首先进行相关BeanDefinition的动态注册,然后当Spring容器注入相关实例时会进行实例的初始化,最后当FeignClient接口类实例的函数被调用时会发送网络请求。

动态注册BeanDefinition

OpenFeign可以通过多种方式进行自定义配置,配置的变化会导致接口类初始化时使用不同的Bean实例,从而控制OpenFeign的相关行为,比如说网络请求的编解码、压缩和日志处理

FeignClientsRegister

@EnableFeignClients有三个作用,一是引入FeignClientsRegistrar;二是指定扫描FeignClient的包信息,就是指定FeignClient接口类所在的包名;三是指定FeignClient接口类的自定义配置类。@EnableFeignClients注解的定义如下所示.

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

	/**
	 * basePackages()属性的别名。 允许使用更简洁的注释声明,例如: @ComponentScan("org.my.pkg")而不是@ComponentScan(basePackages="org.my.pkg") 。
	 * @return the array of 'basePackages'.
	 */
	String[] value() default {};

	/**
	 * 基本软件包以扫描带注释的组件。
value()是此属性的别名(并与该属性互斥)。
使用basePackageClasses()作为基于字符串的软件包名称的类型安全替代方法
	 *
	 * @return the array of 'basePackages'.
	 */
	String[] basePackages() default {};

	/**
	 * basePackages()类型安全替代方法,用于指定要扫描的组件以扫描带注释的组件。 指定类别的包装将被扫描。
考虑在每个程序包中创建一个特殊的无操作标记类或接口,该类或接口除了被该属性引用外没有其他用途。.
	 *
	 * @return the array of 'basePackageClasses'.
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * 自定义feign client的自定义配置,例如feign.codec.Decoder , feign.codec.Encoder和feign.Contract 组件。
	 */
	Class<?>[] defaultConfiguration() default {};

	/**
	 * 用@FeignClient注释的类的列表。 如果不为空,则禁用类路径扫描
	 * @return
	 */
	Class<?>[] clients() default {};
}

FeignClientsRegistrar是ImportBeanDefinitionRegistrar的子类,Spring用ImportBeanDefinitionRegistrar来动态注册BeanDefinition。OpenFeign通过FeignClientsRegistrar来处理@FeignClient修饰的FeignClient接口类,将这些接口类的BeanDefinition注册到Spring容器中,这样就可以使用@Autowired等方式来自动装载这些FeignClient接口类的Bean实例。FeignClientsRegistrar的部分代码如下所示。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, EnvironmentAware {

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

	public FeignClientsRegistrar() {
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@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注解修饰的FeignCleint接口类,然后进行Bean实例注册。@EnableFeignClients的自定义配置类是被@Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例。这些组件包括:Client、Targeter、Decoder、Encoder和Contract等。接下来看看registerDefaultConfiguration的代码实现,如下所示

private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    // 获取到metadata中关于enableFeignClients键值队
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 如果EnableFeignClients配置了defaultConfiguration那么进行下一步操作,如果没有使用默认的FeignConfiguration
		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"));
		}
	}
// ,registerDefaultConfiguration方法会判断@EnableFeignClients注解是否设置了defaultConfiguration属性。如果有,则将调用registerClientConfiguration方法,进行BeanDefinitionRegistry的注册
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
    // 使用BeanDefinitionBuilder来生成BeanDedinition,并注册到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的名称.

FeignClientSpecification类实现了NamedContextFactory.Specification接口,它是OpenFeign组件实例化的重要一环,它持有自定义配置类提供的组件实例,供OpenFeign使用。Spring Cloud框架使用NamedContextFactory创建一系列的运行上下文(ApplicationContext),来让对应的Specification在这些上下文中创建实例对象。这样使得各个子上下文中的实例对象相互独立,互不影响,可以方便地通过子上下文管理一系列不同的实例对象。NamedContextFactory有三个功能,一是创建AnnotationConfigApplicationContext子上下文;二是在子上下文中创建并获取Bean实例;三是当子上下文消亡时清除其中的Bean实例。在OpenFeign中,FeignContext继承了NamedContextFactory,用于存储各类OpenFeign的组件实例。

cIyDYR.png

FeignAutoConfiguration是OpenFeign的自动配置类,它会提供FeignContext实例。并且将之前注册的FeignClientSpecification通过setConfigurations方法设置给FeignContext实例。这里处理了默认配置类FeignClientsConfiguration和自定义配置类的替换问题。如果FeignClientsRegistrar没有注册自定义配置类,那么configurations将不包含FeignClientSpecification对象,否则会在setConfigurations方法中进行默认配置类的替换。FeignAutoConfiguration的相关代码如下所示

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {

	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public HasFeatures feignFeature() {
		return HasFeatures.namedFeature("Feign", Feign.class);
	}

	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}
}
/FeignContext.java
/**
 创建伪装类实例的工厂。 它为每个客户端名称创建一个Spring ApplicationContext,并从那里提取所需的bean。
 * @author Spencer Gibb
 * @author Dave Syer
 */
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

	public FeignContext() {
        // 将默认的FeignClientConfiguration作为参数传递给构造函数
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}

}

NamedContextFactory是FeignContext的父类,其createContext方法会创建具有名称的Spring的AnnotationConfigApplicationContext实例作为当前上下文的子上下文。这些AnnotationConfigApplicationContext实例可以管理OpenFeign组件的不同实例。NamedContextFactory的实现如下代码所示

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap();
    private Map<String, C> configurations = new ConcurrentHashMap();
    private ApplicationContext parent;
    private Class<?> defaultConfigType;
    private final String propertySourceName;
    private final String propertyName;

    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
        this.defaultConfigType = defaultConfigType;
        this.propertySourceName = propertySourceName;
        this.propertyName = propertyName;
    }

    public void setApplicationContext(ApplicationContext parent) throws BeansException {
        this.parent = parent;
    }

    public void setConfigurations(List<C> configurations) {
        Iterator var2 = configurations.iterator();

        while(var2.hasNext()) {
            C client = (NamedContextFactory.Specification)var2.next();
            this.configurations.put(client.getName(), client);
        }

    }

    public Set<String> getContextNames() {
        return new HashSet(this.contexts.keySet());
    }
	// 由于NamedContextFactory实现了DisposableBean接口,当NamedContextFactory实例消亡时,Spring框架会调用其destroy方法,清除掉自己创建的所有子上下文和自身包含的所有组件实例
    public void destroy() {
        Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
        Iterator var2 = values.iterator();

        while(var2.hasNext()) {
            AnnotationConfigApplicationContext context = (AnnotationConfigApplicationContext)var2.next();
            context.close();
        }

        this.contexts.clear();
    }

    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized(this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, this.createContext(name));
                }
            }
        }

        return (AnnotationConfigApplicationContext)this.contexts.get(name);
    }

    protected AnnotationConfigApplicationContext createContext(String name) {
        // 通过注解的形式获取上下文对象
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 获取该name所对应的configuration,如果有的话就注册到Context中
        if (this.configurations.containsKey(name)) {
            Class[] var3 = ((NamedContextFactory.Specification)this.configurations.get(name)).getConfiguration();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Class<?> configuration = var3[var5];
                context.register(new Class[]{configuration});
            }
        }
		// 注册default的Configuration,也就是FeignClientRegister类的registerDefaultConfiguration方法中的注册的Configuration
        Iterator var9 = this.configurations.entrySet().iterator();

        while(true) {
            Entry entry;
            do {
                if (!var9.hasNext()) {
                    // 注册PropertyPlaceholderAutoConfiguration和FeignClientsConfiguration配置;类
                    context.register(new Class[]{PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType});
                    // 设置子Context的Environment的propertySource属性源
                    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));
                    // 所有的context的parent都相同,这样的话,一些相同的Bean可以通过parent context来获取
                    if (this.parent != null) {
                        context.setParent(this.parent);
                    }

                    context.setDisplayName(this.generateDisplayName(name));
                    context.refresh();
                    return context;
                }

                entry = (Entry)var9.next();
            } while(!((String)entry.getKey()).startsWith("default."));

            Class[] var11 = ((NamedContextFactory.Specification)entry.getValue()).getConfiguration();
            int var12 = var11.length;

            for(int var7 = 0; var7 < var12; ++var7) {
                Class<?> configuration = var11[var7];
                context.register(new Class[]{configuration});
            }
        }
    }
    ....
        .....
}

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

扫描类信息

FeignClientsRegistrar做的第二件事情是扫描指定包下的类文件,注册@FeignClient注解修饰的接口类信息。

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    // 生产自定义的ClassPathScanningCandidateComponentProvider
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;
	// 获取EnableFeignClients所有属性的键值对
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
    // 依照注解进行TypeFilter,只会扫描出被FeignClient修饰的类
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
    // 如果没有设置clients属性,那么需要扫描basePackage,所以设置了AnnotationTypeFilter并且去获取basePackage
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
            //设置了AnnotationTypeFilter并且去获取basePackage
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}
		// 遍历获取到的basepackage列表
		for (String basePackage : basePackages) {
            // 获取basepackage列表下所有的BeanDefinition
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");
					// 从BeanDefinition中获取FeignClient的属性值
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
                    // 对于单独某个Feignclient的configuration进行配置
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					// 注册FeignClient的BeanDefinition
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

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

FeignClientsRegistrar的registerFeignClients方法依据@EnableFeignClients的属性获取要扫描的包路径信息,然后获取这些包下所有被@FeignClient注解修饰的接口类的BeanDefinition,最后调用registerFeignClient动态注册BeanDefinition。registerFeignClients方法中有一些细节值得认真学习,有利于加深了解Spring框架。首先是如何自定义Spring类扫描器,即如何使用ClassPathScanningCandidateComponentProvider和各类TypeFilter。OpenFeign使用了AnnotationTypeFilter,来过滤出被@FeignClient修饰的类,getScanner方法的具体实现如下所示.

// FeignClientsRegistrar.java
protected ClassPathScanningCandidateComponentProvider getScanner() {
		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
			@Override
			protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
				boolean isCandidate = false;
                // 判断beanDefinition是否为内部类,否则直接返回false
				if (beanDefinition.getMetadata().isIndependent()) {
                    // 判断是否为接口类,所实现的接口只有一个,并且该接口是Annotation否则返回true
					if (!beanDefinition.getMetadata().isAnnotation()) {
						isCandidate = true;
					}
				}
				return isCandidate;
			}
		}
	}

ClassPathScanningCandidateComponentProvider的作用是遍历指定路径的包下的所有类。比如指定包路径为com/test/openfeign,它会找出com.test.openfeign包下所有的类,将所有的类封装成Resource接口集合。Resource接口是Spring对资源的封装,有FileSystemResource、ClassPathResource、UrlResource等多种实现。接着ClassPathScanning CandidateComponentProvider类会遍历Resource集合,通过includeFilters和excludeFilters两种过滤器进行过滤操作。includeFilters和excludeFilters是TypeFilter接口类型实例的集合,TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤掉;而includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤。如果一个Resource没有被过滤,它会被转换成ScannedGenericBeanDefinition添加到BeanDefinition集合中

实例初始化

FeignClientFactoryBean是工厂类,Spring容器通过调用它的getObject方法来获取对应的Bean实例。被@FeignClient修饰的接口类都是通过FeignClientFactoryBean#getObject()方法来进行实例化的,具体实现如下代码所示:

@Override
	public Object getObject() throws Exception {
		return getTarget();
	}

	/**
	 * @param <T> the target type of the Feign client
	 * @return a {@link Feign} client created with the specified data and the context
	 * information
	 */
	<T> T getTarget() {
        // FeignContext 创建伪装类实例的工厂。 它为每个客户端名称创建一个Spring ApplicationContext,并从那里提取所需的bean
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {
			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();
        // 调用FeignContext的getInstance方法获取Client对象
		Client client = getOptional(context, Client.class);
        // 因为有具体的url所以就饿不需要负载均衡,所以去除loadbalancerFeignClient实例
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			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();
			}
			builder.client(client);
		}
        // Targeter是一个接口,它的target方法会生成对应的实例对象。它有两个实现类,分别为DefaultTargeter和HystrixTargeter.  DefaultTargeter 调用了Feign.Builder的target方法。Feign.Builder负责生成被@FeignClient修饰的FeignClient接口类实例。它通过Java反射机制,构造InvocationHandler实例并将其注册到FeignClient上,当FeignClient的方法被调用时,InvocationHandler的回调函数会被调用,OpenFeign会在其回调函数中发送网络请求
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

这里就用到了FeignContext的getInstance方法,我们在前边已经讲解了FeignContext的作用,getOptional方法调用了FeignContext的getInstance方法,从FeignContext的对应名称的子上下文中获取到Client类型的Bean实例

// FeignClientFactoryBean.java
protected <T> T getOptional(FeignContext context, Class<T> type) {
	return context.getInstance(contextId, type);
}
// 
public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                                                            type).length > 0) {
        // 从对应的context中获取Bean实例,如果对应的子上下文没有则直接从父上下文中获取
        // 在feignAutoConfiguration中 feignClient(){ return new ApacheHttpClient(httpClient)}
        return context.getBean(type);
    }
    return null;
}
扫描函数信息

在扫描FeignClient接口类所有函数生成对应Handler的过程中,OpenFeign会生成调用该函数时发送网络请求的模板,也就是RequestTemplate实例。RequestTemplate中包含了发送网络请求的URL和函数参数填充的信息。@RequestMapping、@PathVariable等注解信息也会包含到RequestTemplate中,用于函数参数的填充。ParseHandlersByName类的apply方法就是这一过程的具体实现。它首先会使用Contract来解析接口类中的函数信息,并检查函数的合法性,然后根据函数的不同类型来为每个函数生成一个BuildTemplateByResolvingArgs对象,最后使用SynchronousMethodHandler.Factory来创建MethodHandler实例。ParseHandlersByName的apply()实现如下代码所示:

public Map<String, MethodHandler> apply(Target target) {
    // 获取type的所有的方法的信息,会根据注解生成每个方法的RequestTemplate
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        if (md.isIgnored()) {
          result.put(md.configKey(), args -> {
            throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
          });
        } else {
          result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
        }
      }
      return result;
    }

OpenFeign默认的Contract实现是SpringMvcContract。SpringMvcContract的父类为BaseContract,而BaseContract是Contract众多子类中的一员,其他还有JAXRSContract和HystrixDelegatingContract等。Contract的parseAndValidateMetadata方法会解析与HTTP请求相关的所有函数的基本信息和注解信息,代码如下所示:

// springMvcContract.java  
@Override
	public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
		processedMethods.put(Feign.configKey(targetType, method), method);
		MethodMetadata md = super.parseAndValidateMetadata(targetType, method);

		RequestMapping classAnnotation = findMergedAnnotation(targetType,
				RequestMapping.class);
		if (classAnnotation != null) {
			// produces - use from class annotation only if method has not specified this
			if (!md.template().headers().containsKey(ACCEPT)) {
				parseProduces(md, method, classAnnotation);
			}

			// consumes -- use from class annotation only if method has not specified this
			if (!md.template().headers().containsKey(CONTENT_TYPE)) {
				parseConsumes(md, method, classAnnotation);
			}

			// headers -- class annotation is inherited to methods, always write these if
			// present
			parseHeaders(md, method, classAnnotation);
		}
		return md;
	}

BaseContract的parseAndValidateMetadata方法会依次解析接口类的注解,函数注解和函数的参数注解,将这些注解包含的信息封装到MethodMetadata对象中,然后返回

// BaseContract.java
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
      final MethodMetadata data = new MethodMetadata();
      data.targetType(targetType);
      data.method(method);
    // 函数的返回值
      data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
    // 函数feign相关的唯一配置键
      data.configKey(Feign.configKey(targetType, method));
	// 获取并处理修饰class的注解信息
      if (targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
      }
    // 调用子类processAnnotationOnClass的实现
      processAnnotationOnClass(data, targetType);

	// 处理修饰method的注解信息	
      for (final Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
      }
      if (data.isIgnored()) {
        return data;
      }
      checkState(data.template().method() != null,
          "Method %s not annotated with HTTP method type (ex. GET, POST)%s",
          data.configKey(), data.warnings());
      final Class<?>[] parameterTypes = method.getParameterTypes();
      final Type[] genericParameterTypes = method.getGenericParameterTypes();
	// 函数参数注解类型
      final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      final int count = parameterAnnotations.length;
    // 依次处理参数注解并且返回该参数来指明是否为将要发送请求的body。除了body外还可能是path,param等
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }

        if (isHttpAnnotation) {
          data.ignoreParamater(i);
        }

        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
          if (data.isAlreadyProcessed(i)) {
            checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
                "Body parameters cannot be used with form parameters.%s", data.warnings());
          } else {
            checkState(data.formParams().isEmpty(),
                "Body parameters cannot be used with form parameters.%s", data.warnings());
            checkState(data.bodyIndex() == null,
                "Method has too many Body parameters: %s%s", method, data.warnings());
            // 表明发送请求body的参数位置和参数类型
            data.bodyIndex(i);
            data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
          }
        }
      }

      if (data.headerMapIndex() != null) {
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],
            genericParameterTypes[data.headerMapIndex()]);
      }

      if (data.queryMapIndex() != null) {
        if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
          checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
        }
      }

      return data;
    }

processAnnotationOnClass方法用于处理接口类注解。该函数在parseAndValidateMetadata方法中可能会被调用两次,如果targetType只继承或者实现一种接口时,先处理该接口的注解,再处理targetType的注解;否则只会处理targetType的注解。@RequestMapping在修饰FeignClient接口类时,其value所代表的值会被记录下来,它是该FeignClient下所有请求URL的前置路径,处理接口类注解的函数代码如下所示:

@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		if (clz.getInterfaces().length == 0) {
            // 获取RequestMapping的注解信息,并设置MethodMetadata.template数据
			RequestMapping classAnnotation = findMergedAnnotation(clz,
					RequestMapping.class);
			if (classAnnotation != null) {
				// Prepend path from class annotation if specified
				if (classAnnotation.value().length > 0) {
					String pathValue = emptyToNull(classAnnotation.value()[0]);
					pathValue = resolve(pathValue);
					if (!pathValue.startsWith("/")) {
						pathValue = "/" + pathValue;
					}
                    // 处理@RequestMapping的value,一般都是发送请求的path
					data.template().uri(pathValue);
				}
			}
		}
	}


processAnnotationOnMethod方法的主要作用是处理修饰函数的注解。它会首先校验该函数是否被@RequestMapping修饰,如果没有就会直接返回。然后获取该函数所对应的HTTP请求的方法,默认的方法是GET。接着会处理@RequestMapping中的value属性,解析value属性中的pathValue,比如说value属性值为/instance/{instanceId},那么pathValue的值就是instanceId。最后处理消费(consumes)和生产(produces)相关的信息,记录媒体类型(media types):

// SpringmvcContract.java
@Override
	protected void processAnnotationOnMethod(MethodMetadata data,
			Annotation methodAnnotation, Method method) {
		if (CollectionFormat.class.isInstance(methodAnnotation)) {
			CollectionFormat collectionFormat = findMergedAnnotation(method,
					CollectionFormat.class);
			data.template().collectionFormat(collectionFormat.value());
		}

		if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
				.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}

		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// HTTP Method
        // 处理http method
		RequestMethod[] methods = methodMapping.method();
		if (methods.length == 0) {
			methods = new RequestMethod[] { RequestMethod.GET };
		}
		checkOne(method, methods, "method");
		data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

		// path
		checkAtMostOne(method, methodMapping.value(), "value");
		if (methodMapping.value().length > 0) {
			String pathValue = emptyToNull(methodMapping.value()[0]);
			if (pathValue != null) {
				pathValue = resolve(pathValue);
				// Append path from @RequestMapping if value is present on method
				if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
					pathValue = "/" + pathValue;
				}
				data.template().uri(pathValue, true);
			}
		}

		// produces 处理生产者
		parseProduces(data, method, methodMapping);

		// consumes 处理消费者
		parseConsumes(data, method, methodMapping);

		// headers 处理头部
		parseHeaders(data, method, methodMapping);

		data.indexToExpander(new LinkedHashMap<>());
	}

// SpringmvcContract.java
private void parseProduces(MethodMetadata md, Method method,
			RequestMapping annotation) {
		String[] serverProduces = annotation.produces();
		String clientAccepts = serverProduces.length == 0 ? null
				: emptyToNull(serverProduces[0]);
		if (clientAccepts != null) {
			md.template().header(ACCEPT, clientAccepts);
		}
	}

	private void parseConsumes(MethodMetadata md, Method method,
			RequestMapping annotation) {
		String[] serverConsumes = annotation.consumes();
		String clientProduces = serverConsumes.length == 0 ? null
				: emptyToNull(serverConsumes[0]);
		if (clientProduces != null) {
			md.template().header(CONTENT_TYPE, clientProduces);
		}
	}

	private void parseHeaders(MethodMetadata md, Method method,
			RequestMapping annotation) {
		// TODO: only supports one header value per key
		if (annotation.headers() != null && annotation.headers().length > 0) {
			for (String header : annotation.headers()) {
				int index = header.indexOf('=');
				if (!header.contains("!=") && index >= 0) {
					md.template().header(resolve(header.substring(0, index)),
							resolve(header.substring(index + 1).trim()));
				}
			}
		}
	}

而processAnnotationsOnParameter方法则主要处理修饰函数参数的注解。它会根据注解类型来调用不同的AnnotatedParameterProcessor的实现类,解析注解的属性信息。函数参数的注解类型包括@RequestParam、@RequestHeader和@PathVariable。processAnnotationsOnParameter方法的具体实现如下代码所示

// springmvcContract.java
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data,
      Annotation[] annotations, int paramIndex) {
   boolean isHttpAnnotation = false;

   AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(
         data, paramIndex);
   Method method = processedMethods.get(data.configKey());
   // 遍历所有的参数注解
   for (Annotation parameterAnnotation : annotations) {
       // 不同的注解类型有不同的Processor
      AnnotatedParameterProcessor processor = annotatedArgumentProcessors
            .get(parameterAnnotation.annotationType());
      if (processor != null) {
         Annotation processParameterAnnotation;
         // synthesize, handling @AliasFor, while falling back to parameter name on
         // missing String #value():
          // 如果没有缓存的processor则生成一个
         processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
               parameterAnnotation, method, paramIndex);
         isHttpAnnotation |= processor.processArgument(context,
               processParameterAnnotation, method);
      }
   }

   if (!isMultipartFormData(data) && isHttpAnnotation
         && data.indexToExpander().get(paramIndex) == null) {
      TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
      if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {
         Param.Expander expander = convertingExpanderFactory
               .getExpander(typeDescriptor);
         if (expander != null) {
            data.indexToExpander().put(paramIndex, expander);
         }
      }
   }
   return isHttpAnnotation;
}

AnnotatedParameterProcessor是一个接口,有三个实现类:PathVariableParameterProcessor、RequestHeaderParameterProcessor和RequestParamParameterProcessor,三者分别用于处理@RequestParam、@RequestHeader和@PathVariable注解.

生成Proxy接口类

ReflectiveFeign#newInstance方法的第二部分就是生成相应接口类的实例对象,并设置方法处理器,如下所示:

public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

OpenFeign使用Proxy的newProxyInstance方法来创建FeignClient接口类的实例,然后将InvocationHandler绑定到接口类实例上,用于处理接口类函数调用.Default实现了InvocationHandlerFactory接口,其create方法返回ReflectiveFeign. FeignInvocationHandler实例。ReflectiveFeign的内部类FeignInvocationHandler是InvocationHandler的实现类,其主要作用是将接口类相关函数的调用分配给对应的MethodToHandler实例,即SynchronousMethodHandler来处理。当调用接口类实例的函数时,会直接调用到FeignInvocationHandler的invoke方法。invoke方法会根据函数名称来调用不同的MethodHandler实例的invoke方法,如下所示:

/**
 * Controls reflective method dispatch.
 */
public interface InvocationHandlerFactory {

  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  /**
   * Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
   * single method.
   */
  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

  static final class Default implements InvocationHandlerFactory {
	// 最终指向的是ReflectiveFeign
    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

  static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
	// dispath是一个map用于分发函数交给对应的MethodHandler
      return dispatch.get(method).invoke(args);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof FeignInvocationHandler) {
        FeignInvocationHandler other = (FeignInvocationHandler) obj;
        return target.equals(other.target);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return target.hashCode();
    }

    @Override
    public String toString() {
      return target.toString();
    }
  }

函数调用和网络请求

在配置和实例生成结束之后,就可以直接使用FeignClient接口类的实例,调用它的函数来发送网络请求。在调用其函数的过程中,由于设置了MethodHandler,所以最终函数调用会执行SynchronousMethodHandler的invoke方法。在该方法中,OpenFeign会将函数的实际参数值与之前生成的RequestTemplate进行结合,然后发送网络请求.

@Override
  public Object invoke(Object[] argv) throws Throwable {
    // 根据函数参数创建RequestTemplate实例,buildTemplateFromArgs是RequestTemplate.Factory接口的实例,在当前是BuildTemplateResolvingArgs类的实例  
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

SynchronousMethodHandler的invoke方法先创建了RequestTemplate对象。在该对象的创建过程中,使用到之前收集的函数信息MethodMetadata。遍历MethodMetadata中参数相关的indexToName,然后根据索引从invoke的参数数组中获得对应的值,将其填入对应的键值对中。然后依次处理查询和头部相关的参数值。invoke方法调用RequestTemplate.Factory 的实现类 ReflectiveFeign类的create方法创建RequestTemplate对象:

// ReflectiveFeign.java
@Override
    public RequestTemplate create(Object[] argv) {
      RequestTemplate mutable = RequestTemplate.from(metadata.template());
      mutable.feignTarget(target);
        // 设置url
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.target(String.valueOf(argv[urlIndex]));
      }
      Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
       // 遍历MethodMeadata中所有关于参数的索引及其对应名称的配置信息
      for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        // entry.getKey()就是参数的索引  
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
          // indexToExpander保存着将各种类型参数的值转换为String类型的Expander转换器  
          if (indexToExpander.containsKey(i)) {
             // 将value转为string
            value = expandElements(indexToExpander.get(i), value);
          }
          for (String name : entry.getValue()) {
            varBuilder.put(name, value);
          }
        }
      }
	// resolve首先会替换URL中的pathValues,然后对URL进行编码,接着将所有头部信息进行转化,最后处理请求的Body数据
      RequestTemplate template = resolve(argv, mutable, varBuilder);
      // 设置queryMap参数 
      if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        Object value = argv[metadata.queryMapIndex()];
        Map<String, Object> queryMap = toQueryMap(value);
        template = addQueryMapQueryParameters(queryMap, template);
      }
	// 设置headerMap参数
      if (metadata.headerMapIndex() != null) {
        template =
            addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
      }

      return template;
    }

我们看下resolve函数:

public RequestTemplate resolve(Map<String, ?> variables) {

    StringBuilder uri = new StringBuilder();

    /* create a new template form this one, but explicitly */
    RequestTemplate resolved = RequestTemplate.from(this);

    if (this.uriTemplate == null) {
      /* create a new uri template using the default root */
      this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset);
    }

    String expanded = this.uriTemplate.expand(variables);
    if (expanded != null) {
      uri.append(expanded);
    }

    /*
     * for simplicity, combine the queries into the uri and use the resulting uri to seed the
     * resolved template.
     */
    if (!this.queries.isEmpty()) {
      /*
       * since we only want to keep resolved query values, reset any queries on the resolved copy
       */
      resolved.queries(Collections.emptyMap());
      StringBuilder query = new StringBuilder();
      Iterator<QueryTemplate> queryTemplates = this.queries.values().iterator();

      while (queryTemplates.hasNext()) {
        QueryTemplate queryTemplate = queryTemplates.next();
        String queryExpanded = queryTemplate.expand(variables);
        if (Util.isNotBlank(queryExpanded)) {
          query.append(queryExpanded);
          if (queryTemplates.hasNext()) {
            query.append("&");
          }
        }
      }

      String queryString = query.toString();
      if (!queryString.isEmpty()) {
        Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);
        if (queryMatcher.find()) {
          /* the uri already has a query, so any additional queries should be appended */
          uri.append("&");
        } else {
          uri.append("?");
        }
        uri.append(queryString);
      }
    }

    /* add the uri to result */
    resolved.uri(uri.toString());

    /* headers */
    if (!this.headers.isEmpty()) {
      /*
       * same as the query string, we only want to keep resolved values, so clear the header map on
       * the resolved instance
       */
      resolved.headers(Collections.emptyMap());
      for (HeaderTemplate headerTemplate : this.headers.values()) {
        /* resolve the header */
        String header = headerTemplate.expand(variables);
        if (!header.isEmpty()) {
          /* split off the header values and add it to the resolved template */
          String headerValues = header.substring(header.indexOf(" ") + 1);
          if (!headerValues.isEmpty()) {
            /* append the header as a new literal as the value has already been expanded. */
            resolved.header(headerTemplate.getName(), Literal.create(headerValues));
          }
        }
      }
    }

    if (this.bodyTemplate != null) {
      resolved.body(this.bodyTemplate.expand(variables));
    }

    /* mark the new template resolved */
    resolved.resolved = true;
    return resolved;
  }

executeAndDecode方法会根据RequestTemplate生成Request对象,然后交给Client实例发送网络请求,最后返回对应的函数返回类型的实例。executeAndDecode方法的具体实现如下所示:

// SynchronousMethodHandler.java
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 根据RequestTemplate生成Request
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    // client发送网路请求,client可能为okhttpClient和apacheClient
    try {
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }
OpenFein Client编解码器的自定义和请求/响应压缩

Encoder用于将Object对象转化为HTTP的请求Body,而Decoder用于将网络响应转化为对应的Object对象。对于二者,OpenFeign都提供了默认的实现,但是使用者可以根据自己的业务来选择其他的编解码方式。只需要在自定义配置类中给出Decoder和Encoder的自定义Bean实例,那么OpenFeign就可以根据配置,自动使用我们提供的自定义实例进行编解码操作。如下代码所示,CustomFeignConfig配置类将ResponseEntityDecoder和SpringEncoder配置为Feign的Decoder与Encoder实例。

 class CustomFeignConfig{
    @Bean
    public Decoder feignDecoder(){
        HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
        ObjectFactory<HttpMessageConverters> objectFactory = ()-> new HttpMessageConverters(jacksonConverter);
        return  new ResponseEntityDecoder(new SpringDecoder(objectFactory));
    }
    @Bean
     public Encoder feignEncoder(){
         HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
         ObjectFactory<HttpMessageConverters> objectFactory = ()-> new HttpMessageConverters(jacksonConverter);
         return  new SpringEncoder(objectFactory);
     }

     private ObjectMapper customObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT,true);
        return objectMapper;
     }
 }

同样还有其他的编码转换器,这个可以自行研究

请求压缩

可以通过下面的属性配置来让OpenFeign在发送请求时进行GZIP压缩:

feign.compression.request.enabled=true
feign.compression.reponse.enabled=true

也可以使用FeignContentGzipEncodingInterceptor来实现请求的压缩,需要在自定义配置文件中初始化该类型的实例,供OpenFeign使用,具体实现如下所示:

public class FeignContentGZipEncodingAutoConfiguration{
    @Bean
    public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties){
        return new FeignContentGzipEncodingInterceptor(properties);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kay三石 [Alay Kay]

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值