Feign简介
-
Feign是一种声明式、模板化的HTTP客户端。声明式调用就像调用本地方法一样调用远程方法,无感知远程HTTP请求,让我们无需关注与远程的交互细节,更无需关注分布式环境开发。
-
集成 Ribbon 和 Eureka 提供的负载均衡的客户端,支持断路器Hystrix,实现服务熔断和降级 。
基本使用
1.引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
2.启动类加注解EnableFeignClients
@SpringBootApplication
@ComponentScan(basePackages = {"com.example","com.mock"})
@EnableFeignClients(basePackages = {"com.example"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.定义接口及方法
@FeignClient(value = "userService")
public interface UserFeignClient {
@RequestMapping("/user/id")
String findById(String id);
}
4.业务中调用
@Resource
private UserFeignClient userFeignClient;
@Override
public String findUser(String id) {
return userFeignClient.findById(id);
}
通过以上方式,让我们在微服务之间调用就如同调用本地方法一般,内部的HTTP远程调用细节全部封装在Feign客户端中。
源码解读
1.启动扫描过程
启动类注解EnableFeignClients引入FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,在IOC容器的启动过程中,会扫描到该接口是实现类并注册为bean definition,最终添加到容器中。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册默认的配置
registerDefaultConfiguration(metadata, registry);
// 为每一个添加了FeignClient的接口注册一个FeignClientFactoryBean
registerFeignClients(metadata, registry);
}
这个过程并没有实例化bean到容器中,只是先解析成BeanDefinition。default.com.example.demo.DemoApplication
第一步,registerDefaultConfiguration()方法:
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取启动类EnableFeignClients注解的属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 如果存在defaultConfiguration,则解析EnableFeignClients配置类
// 然后构造beanName,为default+启动类的全限定名
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"));
}
}
registerClientConfiguration()方法:
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
// 添加name
builder.addConstructorArgValue(name);
// 添加指定的配置
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
这里将EnableFeignClients上配置的defaultConfiguration属性值包装成一个FeignClientSpecification放到容器中。
我们来看FeignClientSpecification这个类的结构,name就是default,configuration就是我们defaultConfiguration属性指定的配置类。
class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
需要注意的是,这里是将我们配置的类包装进一个FeignClientSpecification对象中,即使我们在启动类注解上没有指定配置类,这里仍然会存在一个FeignClientSpecification,只是它的configuration为空而已。
注册默认配置的过程结束。
第二步,registerFeignClients()方法:
EnableFeignClients有几个属性是用来指定FeignClient的,registerFeignClients方法前面部分就是在解析获取需要扫描包的,这里不做细说。
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] clients() default {};
我们来看真正注册FeignClient的代码:
- 解析每个客户端的配置
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");
// 获取FeignClient注解的属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 按照