Preface
In Part introduction to the core Feign implementation principle, at the end of the text also mentions the principle that integration will be re-introduced and the Spring Cloud, Spring has a highly scalable, put some common solutions through the starter to open the way For developers, after introducing the official starter, they usually only need to add some annotations to use related functions (usually @EnableXXX). Let's take a look at how Spring Cloud integrates Feign.
Analysis of Integration Principle
Everything in Spring revolves around Beans, and all Beans are generated based on BeanDefinition. It can be said that BeanDefinition is the cornerstone of the entire Spring empire. The key to this integration is how to generate Feign's corresponding BeanDefinition.
To analyze its integration principle, where do we first start? If you have read first part of , when introducing an example of how to use Spring Cloud, the second step is to add the @EnableFeignClients annotation to the XXXApplication of the project. We can use this as an entry point to analyze it step by step. Implementation principle (usually a considerable part of starters generally add annotations to enable related functions in the startup class).
Enter the @EnableFeignClients annotation, the source code is as follows:
From the source code of the annotation, it can be found that the annotation removes the definition of several parameters (basePackages, defaultConfiguration, clients, etc.), and also introduces the FeignClientsRegistrar class through @Import. The general @Import annotation has the following functions (see official Java Doc specific functions) :
- Declare a Bean
- Import @Configuration annotated configuration class
- Import the implementation class of ImportSelector
- Import the implementation class of (160e1beef2cc8e here use this function )
At this point, it is not difficult to see that the main process of integration is in the FeignClientsRegistrar class. Let us continue to dive into the source code of the FeignClientsRegistrar class.
From the source code, we can see that FeignClientsRegistrar implements the ImportBeanDefinitionRegistrar interface. From the name of the interface, it is not difficult to see that its main function is to inject the BeanDefinition that needs to be initialized into the container. The two method functions of the interface definition are used to inject a given BeanDefinition. Customize beanName (by implementing the BeanNameGenerator interface to customize the logic of beanName generation), and the other uses the default rules to generate beanName (the first letter of the class name is in lowercase format). The interface source code is as follows:
Friends who have some knowledge of Spring know that Spring will initialize the class according to the attribute information of BeanDefinition during the startup of the container and inject it into the container. So the ultimate goal of the generated proxy class into the Spring container.
Although the source code of the FeignClientsRegistrar class seems to be more, from the perspective of its end goal, we mainly look at how to generate BeanDefinition. Through the source code, we can find that it implements the ImportBeanDefinitionRegistrar interface and rewrites the registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry) method. In this method, some BeanDefinition generation and registration work are completed. The source code is as follows:
The whole process is mainly divided into the following two steps:
- Create a BeanDefinition object for the global default configuration of @EnableFeignClients (annotated defaultConfiguration attribute) and inject it into the container (corresponding to step ① in the figure above)
- Create a BeanDefinition object for the class marked with @FeignClient and inject it into the container (corresponding to step ② in the figure above)
Let's dive into the method source code implementation to see its specific implementation principles. First, let's take a look at the first step method registerDefaultConfiguration(AnnotationMetadata, BeanDefinitionRegistry). The source code is as follows:
It can be seen that here is just to get the value of the default configuration property defaultConfiguration of the annotation @EnableFeignClients, the final function implementation is handed over to the registerClientConfiguration(BeanDefinitionRegistry, Object, Object) method to complete, continue to follow up this method, the source code is as follows:
As you can see, the global default configuration of BeanClazz is FeignClientSpecification, and then the global default configuration configuration is set as the input parameter of the BeanDefinition constructor, and then this parameter is passed in when the constructor is instantiated. At this point, the global default configuration of @EnableFeignClients (annotated defaultConfiguration attribute) has been created and injected into the container. The first step is complete, and the whole is relatively simple.
Let's take a look at how the second step creates a BeanDefinition object for the class marked with @FeignClient and injects it into the container . In the second step of the method registerFeignClients(AnnotationMetadata, BeanDefinitionRegistry) implementation, because the method implementation code is more, the use of screenshots will be more scattered, so use the method of posting the source code and adding the necessary comments in the relevant position:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 最终获取到有 @FeignClient 注解类的集合
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取 @EnableFeignClients 注解的属性 map
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 获取 @EnableFeignClients 注解的 clients 属性
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 如果 @EnableFeignClients 注解未指定 clients 属性则扫描添加(扫描过滤条件为:标注有 @FeignClient 的类)
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
// 如果 @EnableFeignClients 注解已指定 clients 属性,则直接添加,不再扫描(从这里可以看出,为了加快容器启动速度,建议都指定 clients 属性)
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍历最终获取到的 @FeignClient 注解类的集合
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());
// 获取 clientName 的值,也就是在构造器的参数值(具体获取逻辑可以参见 getClientName(Map<String, Object>) 方法
String name = getClientName(attributes);
// 同上文第一步最后调用的方法,注入 @FeignClient 注解的配置对象到容器中
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注入 @FeignClient 对象,该对象可以在其它类中通过 @Autowired 直接引入(e.g. XXXService)
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
From the source code, you can see that the last is the @FeignClient object injected by the method registerFeignClient(BeanDefinitionRegistry, AnnotationMetadata, Map<String, Object>). Continue to dive into this method. The source code is as follows:
The method implementation is relatively long, and the ultimate goal is to construct a BeanDefinition object, and then inject it into the container through BeanDefinitionReaderUtils.registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry).
The key step is to obtain information from the @FeignClient annotation and set it in the BeanDefinitionBuilder. The class registered in BeanDefinitionBuilder is FeignClientFactoryBean. The function of this class is just like its name to create FeignClient beans, and then Spring will follow FeignClientFactoryBean Generate objects and inject them into the container.
One thing to be clear is that, in fact, the FeignClientFactoryBean class is finally injected into the container. Spring will generate instance objects based on this class when the class is initialized, which is to call the FeignClientFactoryBean.getObject() method. This generated object is The proxy object we actually use. Next, enter the getObject() method of the FeignClientFactoryBean class. The source code is as follows:
You can see that this method is another method getTarget() in the directly called class. I will continue to follow up on this method. Since this method has a lot of implementation code, the use of screenshots will be more scattered, so use the source code and post the relevant Add the necessary comments to the location:
/**
* @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() {
// 从 Spring 容器中获取 FeignContext Bean
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
// 根据获取到的 FeignContext 构建出 Feign.Builder
Feign.Builder builder = feign(context);
// 注解 @FeignClient 未指定 url 属性
if (!StringUtils.hasText(url)) {
// url 属性是固定访问某一个实例地址,如果未指定协议则拼接 http 请求协议
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
// 格式化 url
url += cleanPath();
// 生成代理和我们之前的代理一样,注解 @FeignClient 未指定 url 属性则返回一个带有负载均衡功能的客户端对象
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
// 注解 @FeignClient 已指定 url 属性
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
// 获取一个 client
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
// 这里没有负载是因为我们有指定了 url
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
// 生成代理和我们之前的代理一样,最后被注入到 Spring 容器中
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
From the source code, we know that FeignClientFactoryBean inherits FactoryBean, and its method FactoryBean.getObject returns Feign's proxy object. Finally, this proxy object is injected into the Spring container, and we can directly inject it through @Autowired. At the same time, you can also find that the above code branch will eventually go to the following code:
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
Click into the source code of targeter.target, and you can see that what is actually created here is a proxy object, which means that when the container starts, a proxy object is created for each @FeignClient. At this point, the core implementation of the integration principle of Spring Cloud and Feign is introduced.
to sum up
This article mainly introduces the principle of Spring Cloud integration with Feign. Through the above introduction, you already know that Srpring will create a proxy object with our marked @FeignClient interface, then with this proxy object we can do enhanced processing (eg pre-enhancement, post-enhancement), then Do you know how to achieve it? Interested friends can look through the source code to find the answer (warm reminder: the enhanced logic is in the InvocationHandler). There is also collaboration between Feign and Ribbon and Hystrix components. Interested friends can download the source code to learn about it.
Thank you all for your likes, favorites and comments. See you next week! The article is continuously updated, and a search on WeChat "mghio" pays attention to this "Java porter & lifelong learner", let's be awesome~.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。