在上篇文章我们看了服务注册与发现Eureka,今天我们看下服务之间的调用组件。
文章目录
-
-
- 各组件深入之Spring Cloud openFeign
-
- FeignClient 的配置参数
- Feign原理:
- Feign源码解析
-
- 核心组件
- 动态注册BeanDefinition
-
- FeignClientsRegister
- 扫描类信息
- 实例初始化
-
- 扫描函数信息
- 生成Proxy接口类
- 函数调用和网络请求
- OpenFein Client编解码器的自定义和请求/响应压缩
-
各组件深入之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> {
@Overridepublic DemobServiceClient create(Throwable cause) {
DemobServiceClientFallback demobServiceClientFallback = new DemobServiceClientFallback();demobServiceClientFallback.setCause(cause);return demobServiceClientFallback;}}
@Slf4j
public class DemobServiceClientFallback implements DemobServiceClient {
@Setterprivate Throwable cause;@Overridepublic 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可选 | |
decode404 | false | 配置响应状态码为404时是否应该抛出FeignExceptions | |
configuration | {} | 自定义当前feign client的一些配置 | 参考FeignClientsConfiguration |
fallback | void.class | 熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。 | 底层依赖hystrix,启动类要加上@EnableHystrix |
path | 空字符串 | 自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping | |
primary | true |
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相应方法被调用时发送网络请求,然后再将请求响应转化为函数返回值进行输出。
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 RibbonClientsConfigurationRegistgrarprivate ResourceLoader resourceLoader;private Environment environment;public FeignClientsRegistrar() {
}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;}@Overridepublic 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那么进行下一步操作,如果没有使用默认的FeignConfigurationif (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,并注册到registryBeanDefinitionBuilder 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的组件实例。
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<>();@Beanpublic HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);}@Beanpublic 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方法中的注册的ConfigurationIterator 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) {
// 生产自定义的ClassPathScanningCandidateComponentProviderClassPathScanningCandidateComponentProvider 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并且去获取basePackageif (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}else {
//设置了AnnotationTypeFilter并且去获取basePackagefinal 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() {
@Overrideprotected 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列表下所有的BeanDefinitionSet<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interfaceAnnotatedBeanDefinition 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的BeanDefinitionregisterFeignClient(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 nullbeanDefinition.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) {
@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;// 判断beanDefinition是否为内部类,否则直接返回falseif (beanDefinition.getMetadata().isIndependent()) {
// 判断是否为接口类,所实现的接口只有一个,并且该接口是Annotation否则返回trueif (!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()方法来进行实例化的,具体实现如下代码所示:
@Overridepublic 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,并从那里提取所需的beanFeignContext 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 unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((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的所有的方法的信息,会根据注解生成每个方法的RequestTemplateList<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
@Overridepublic 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 thisif (!md.template().headers().containsKey(ACCEPT)) {
parseProduces(md, method, classAnnotation);}// consumes -- use from class annotation only if method has not specified thisif (!md.template().headers().containsKey(CONTENT_TYPE)) {
parseConsumes(md, method, classAnnotation);}// headers -- class annotation is inherited to methods, always write these if// presentparseHeaders(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的前置路径,处理接口类注解的函数代码如下所示:
@Overrideprotected 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 specifiedif (classAnnotation.value().length > 0) {
String pathValue = emptyToNull(classAnnotation.value()[0]);pathValue = resolve(pathValue);if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue;}// 处理@RequestMapping的value,一般都是发送请求的pathdata.template().uri(pathValue);}}}}
processAnnotationOnMethod方法的主要作用是处理修饰函数的注解。它会首先校验该函数是否被@RequestMapping修饰,如果没有就会直接返回。然后获取该函数所对应的HTTP请求的方法,默认的方法是GET。接着会处理@RequestMapping中的value属性,解析value属性中的pathValue,比如说value属性值为/instance/{instanceId},那么pathValue的值就是instanceId。最后处理消费(consumes)和生产(produces)相关的信息,记录媒体类型(media types):
// SpringmvcContract.java
@Overrideprotected 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 methodRequestMethod[] 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()));// pathcheckAtMostOne(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 methodif (!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 keyif (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) {
// 不同的注解类型有不同的ProcessorAnnotatedParameterProcessor 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@Overridepublic 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);}@Overridepublic 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用于分发函数交给对应的MethodHandlerreturn dispatch.get(method).invoke(args);}@Overridepublic boolean equals(Object obj) {
if (obj instanceof FeignInvocationHandler) {
FeignInvocationHandler other = (FeignInvocationHandler) obj;return target.equals(other.target);}return false;}@Overridepublic int hashCode() {
return target.hashCode();}@Overridepublic String toString() {
return target.toString();}}
函数调用和网络请求
在配置和实例生成结束之后,就可以直接使用FeignClient接口类的实例,调用它的函数来发送网络请求。在调用其函数的过程中,由于设置了MethodHandler,所以最终函数调用会执行SynchronousMethodHandler的invoke方法。在该方法中,OpenFeign会将函数的实际参数值与之前生成的RequestTemplate进行结合,然后发送网络请求.
@Overridepublic 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
@Overridepublic RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());mutable.feignTarget(target);// 设置urlif (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转为stringvalue = 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 valuesObject 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生成RequestRequest request = targetRequest(template);if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();// client发送网路请求,client可能为okhttpClient和apacheClienttry {
response = client.execute(request, options);// ensure the request is set. TODO: remove in Feign 12response = 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{
@Beanpublic Decoder feignDecoder(){
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());ObjectFactory<HttpMessageConverters> objectFactory = ()-> new HttpMessageConverters(jacksonConverter);return new ResponseEntityDecoder(new SpringDecoder(objectFactory));}@Beanpublic 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{@Beanpublic FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties){return new FeignContentGzipEncodingInterceptor(properties);}
}