OpenFeign核心知识小结
# 什么是OpenFeign,它是解决什么问题呢?
在Spring服务之间的调用若基于WebClient实现调用则会出现很多很业务无关的代码如下所示:
String result = webClientBuilder.build()
.get()
//http://file 会被nacos等价替换成file的ip+端口号
.uri("http://file/file/test/hello")
.retrieve()
.bodyToMono(String.class)
.block();
2
3
4
5
6
7
所以OpenFeign就诞生了,我们只需一些简单的配置,即可实现服务之间的远程调用,改造完成后的代码如下所示,相对简洁了许多。
return fileService.hello();
# 能不能给我演示一下OpenFeign的使用?
首先需要进行远程调用的服务必须引入下面这个依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2
3
4
5
6
然后启动类添加这个注解,注意basePackages 为包扫描路径
@EnableFeignClients(basePackages = {"com.course"})
完成后创建远程服务调用的接口,如下所示FeignClient的value即远程调用服务注册到nacos上的服务名,path为请求通用路径,后续GetMapping这些则是具体请求地址了
@FeignClient(value = "file", path = "/file")
public interface FileService {
//GetMapping指向调用地址
@GetMapping("/test/hello")
String hello();
}
2
3
4
5
6
7
8
完成上述配置后,我们将接口注入就行了。
@Autowired
private FileService fileService;
@GetMapping("/test")
public String hello() {
return fileService.hello();
}
2
3
4
5
6
7
8
# OpenFeign包扫描原理了解嘛?
回答这个问题我们首先要从启动类上的注解说起EnableFeignClients这个注解@Import(FeignClientsRegistrar.class),由此可知这个注解会将FeignClientsRegistrar导入,所以我们不妨查看一下FeignClientsRegistrar。
可以看到它有一个registerFeignClients方法,我们不妨步入看看它的核心流程:
@Override
public void FeignClientsRegistrar(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
//注册 FeignClient
registerFeignClients(metadata, registry);
}
2
3
4
5
6
7
源代码如下,可以看到在获取包路径之后就会拿到关于FeignClient注解的bean注入到Spring容器中,进行后续的IOC操作。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
....略
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
//获取扫描的包路径
basePackages = getBasePackages(metadata);
}
.....
//遍历包路径
for (String basePackage : basePackages) {
//找到带有FeignClient注解的的BeanDefinition
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
....
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//完成后将bean注册到Spring容器中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Spring如何完成OpenFeign注册的?
上一个问题中源码包扫描获取到带有FeignClient的bean之后,执行了一个registerFeignClient方法,这就是完成注册的关键,源码如下,可以看到完成上一步包扫描到FeignClient后会不断遍历将这些bean注册到容器中。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//创建FeignClient动态代理构造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//给这个构建器设置当前bean的信息
.....
definition.addPropertyValue("type", className);
//生成beanDefinition
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
//生成BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//会将这些bean注入到Spring容器的beanDefinitionMap中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# OpenFeign是如何实现动态代理的
上一个问题的registerFeignClient方法,可以看到这样一个代码段,可以看到genericBeanDefinition时用到了FeignClientFactoryBean来创建一个BeanDefinition构建器。然后通过这个构建器在Spring后续IOC的逻辑完成依赖注入。
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
2
所以我们不妨看看FeignClientFactoryBean源码,查看它是如何完成对象创建的:
可以看到它继承FactoryBean<Object>,并重写了bean工厂的getObject方法,我们不妨步入看看核心逻辑
@Override
public Object getObject() throws Exception {
return getTarget();
}
2
3
4
可以看到getTarget通过feign方法获取一个构建器,然后通过loadBalance完成代理对象的创建。
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
.......略
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我们继续看看loadBalance逻辑,可以看到调用一个target生成代理对象,我们不妨步入看看
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
//调用target方法动态生成对象
return targeter.target(this, builder, context, target);
}
.....略
}
2
3
4
5
6
7
8
9
10
11
12
不断步入即可看到ReflectiveFeign的newInstance,实现对FeignClient中的每一个方法进行拦截,然后生成一个InvocationHandler ,从而完成代理对象的创建。
@Override
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>();
//遍历每个方法生成methodHandler然后存放到methodToHandler中
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)));
}
}
//拿着methodToHandler和目标类生成代理对象
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# OpenFeign是如何如何解析MVC注解的知道吗?
我们上文提到了实现对FeignClient的BeanDefinition的FeignClientFactoryBean构建器,在Spring IOC阶段,这个构建器的逻辑就开始工作了。
如下源码所示,IOC会在doGetBean时走到FeignClientFactoryBean的getTarget,
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
.......
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
}
2
3
4
5
6
7
8
9
10
11
然后不断步进,走到ReflectiveFeign的newInstance,有一个代码段targetToHandlersByName.apply(target),这就是解析Spring MVC注解的关键。我们不妨步入看看
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
}
2
3
4
可以看到这个方法内部会执行一个parseAndValidateMetadata
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
}
2
3
4
最终代码会走到SpringMvcContract的parseAndValidateMetadata,它会完成对MVC注解的解析从而生成元数据
@Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
this.processedMethods.put(Feign.configKey(targetType, method), method);
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
}
2
3
4
5
6
完成元数据解析之后,ReflectiveFeign就会以FeignClient作为key,元数据作为value放到一个map中

这个map就是后续创建动态代理的原料,参见ReflectiveFeign的newInstance
@Override
public <T> T newInstance(Target<T> target) {
//上文解析MVC注解的入口
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
......
//将MVC解析后的生成的nameToHandler 存到methodToHandler中
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
//methodToHandler生成代理的handler
InvocationHandler handler = factory.create(target, methodToHandler);
//创建动态代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# OpenFeign发送请求的过程了解嘛?
这个问题,我们不妨从调用处插个debug看看,以笔者为例fileService就是笔者创建的一个FeignClient,我们在fileService.hello()插个断点
@Autowired
private FileService fileService;
@GetMapping("/test")
public String hello() {
//调用远程服务fileService
return fileService.hello();
}
2
3
4
5
6
7
8
执行调用时,代码走到的ReflectiveFeign的invoke方法,从而走到下面这段代码中。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
.....
return dispatch.get(method).invoke(args);
}
2
3
4
5
6
从断点我们就可以看到dispatch中存档的就是存放的就是以类名+方法名为key,元数据各种元数据信息(上文解析MVC生成的数据metadata)组装而成的SynchronousMethodHandler为value的map
通过dispatch.get(method)获取到对应的SynchronousMethodHandler,执行invoke方法。对此我们不妨步进看看invoke方法。
可以看到dispatch中存档的就是存放的就是以类名+方法名为key,元数据各种信息组装而成的SynchronousMethodHandler为value的map

最终来到了SynchronousMethodHandler的executeAndDecode,完成请求组装,发起请求,返回响应数据
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//基于元数据等参数构建而成的RequestTemplate 生成request,包含请求地址等信息
Request request = targetRequest(template);
//发起请求并得到响应结果
response = client.execute(request, options);
.....
return response;
2
3
4
5
6
7
8
9
# OpenFeign如何处理响应的?
在上文发出请求后得到result,最后会执行下面这段逻辑,他就会将response的结果解析成feign调用要求的返回值的结果,它会根据代理要求的returnType找到对应的HttpMessageConverter解析出真正的结果返回给用户
Object result = decode(response);