Feign单独使用时,可以对配置的静态服务发起Http调用;当Feign结合Ribbon时,对配置的静态服务可以根据负载均衡策略进行调用;再结合Eureka,可以配合注册中心以及负载均衡策略动态发起调用。本篇分析Feign对配置的静态服务是如何发起Http调用的
一、 Maven依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
二 、自动配置类
Feign中的自动配置类是FeignAutoConfiguration,例举出其中重要的两个Bean
- FeignContext
- Targeter
FeignContext内部还有内置配置类FeignClientsConfiguration,这是创建服务提供方IOC容器时会被解析的配置类,例举出几个重要的Bean - Decoder
- Encoder
- Feign.Builder
- FeignLoggerFactory
- Contract
三、 Bean释义
- FeignContext
托管给容器的一个Bean,在以服务提供方为维度初始化子容器时会被使用到
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
- Targeter
用以生成Fiegn代理类
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
服务提供方维度的Bean
- Decoder
编码器,用以对请求的参数做编码
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
- Encoder
解码器,用以对服务端的响应做解码
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
- Retryer
重试策略,默认不重试
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
- Builder
用以封装FeignClient上所有配置信息
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
- Contract
以类似于SpringMvc约定的参数格式来解析FeignClient接口的参数
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
关于Feign的使用在官网有介绍:需要建立合适的FeignClient,配置好静态服务的节点信息,最终使用注解开启Feign功能。然后便可以用方法调用的形式来代替传统编写Http请求的步骤,极大简化了开发过程。而关于整个Feign可以分为开启与使用两步,开启:对配置的服务提供方资源进行收集并创建代理类;使用:当调用方法是,交由实际创建的代理类来请求服务提供方的资源
四 、FeignClient类收集
Feign的开启需要使用注解@EnableFeignClients,这也是整个Feign的入口
- 在能被容器扫描的类上配置注解@EnableFeignClients后,启动服务
- Spring处理该类时,会从类上注解中收集导入的ImportBeanDefinitionRegistrar,随后调用它们的registerBeanDefinitions方法
- @EnableFeignClients上导入的类是FeignClientsRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
日常使用中,一个FeignClient类对应于一个服务,类中不同的方法对应与服务中不同的接口。这样配置之后,对不同类不同方法的调用,可以达到对不同服务不同接口的发起Http调用。
- 在FeignClientsRegistrar的registerBeanDefinitions方法中,分别为注册默认配置类和注册FeignClient类
- 在registerFeignClients中注册FeignClient类
- 使用容器的Scanner,配合注解类型过滤器,从配置的或者默认的路径中,扫描出带有注解@FeignClient的类形成BeanDefinition
- 在registerFeignClient中尝试将这些BeanDefinition注册到容器中
- 在构造BeanDefinitionBuilder时,属性contextId实际是@FeignClient注解上配置的value或者name属性,用以区分服务提供方,BeanDefinitionBuilder持有的BeanDefinition的BeanClass已经被设置为FeignClientFactoryBean(这是一个FactoryBean,在Bean需要往容器托管时,其getObject方法会被调用),最后构造完成后注册到容器中。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
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)));
}
for (String basePackage : basePackages) {
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");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map&