在上篇文章我们看了服务注册与发现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可选 | |
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 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的组件实例。
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);
}
}