OpenFeign is a remote client request proxy. Its basic function is to enable developers to implement remote calls in an interface-oriented manner, thereby shielding the complexity of underlying communication. Its specific principle is shown in the following figure.
In today's content, we need to analyze the working principle and source code of OpenFeign in detail, and we continue to return to this code.
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
IGoodsServiceFeignClient goodsServiceFeignClient;
@Autowired
IPromotionServiceFeignClient promotionServiceFeignClient;
@Autowired
IOrderServiceFeignClient orderServiceFeignClient;
/**
* 下单
*/
@GetMapping
public String order(){
String goodsInfo=goodsServiceFeignClient.getGoodsById();
String promotionInfo=promotionServiceFeignClient.getPromotionById();
String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
return result;
}
}
From this code, first lead to thinking about the implementation of OpenFeign functions.
- How is the interface declaring the
@FeignClient
annotation parsed and injected? - Through
@Autowired
dependency injection, what kind of instance is injected? - After the interface declared based on FeignClient is resolved, how to store it?
- What does the overall workflow look like when a method call is made?
- How does OpenFeign integrate Ribbon for load balancing analysis?
With these questions, start to analyze the core source code of OpenFeign item by item
OpenFeign annotation scanning and parsing
Think, an interface declared with@FeignClient
annotation uses@Autowired
for dependency injection, and finally this interface can be injected into the instance normally.
From this result, two conclusions can be drawn
- The interface declared by
@FeignClient
will be resolved when the Spring container starts. - Since the interface is loaded by the Spring container, and the interface has no implementation class, a dynamic proxy class will be generated when the Spring container resolves.
EnableFeignClient
When is the @FeignClient
annotation parsed? Based on all the knowledge we have accumulated before, nothing more than the following
- ImportSelector, bulk import beans
- ImportBeanDefinitionRegistrar, import bean declaration and register
- BeanFactoryPostProcessor , a pre- and post-processor where a bean is loaded
Among these options, it seems that ImportBeanDefinitionRegistrar
is more suitable, because the first is to batch import a string collection of a bean, which is not suitable for dynamic bean declaration. And BeanFactoryPostProcessor
is a handler called before and after bean initialization.
In our FeignClient declaration, there are no Spring-related annotations, so naturally it will not be loaded and triggered by the Spring container.
So where is @FeignClient
declared to be scanned?
When integrating FeignClient, we declared an annotation @EnableFeignClients(basePackages = "com.gupaoedu.ms.api")
in the main method of SpringBoot. This annotation requires a specified package name to be filled in.
Well, seeing this, you can basically guess that this annotation must have a great relationship with the analysis of the @FeignClient
annotation.
The following code is the declaration of @EnableFeignClients
annotation, and I saw a very familiar face FeignClientsRegistrar
.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar
FeignClientRegistrar, the main function is to scan and inject into the IOC container for the interface that declares the @FeignClient
annotation.
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}
Sure enough, this class implements the ImportBeanDefinitionRegistrar
interface
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
This interface has two overloaded methods for implementing Bean declaration and registration.
Simply show you the role of ImportBeanDefinitionRegistrar.
In the
gpmall-portal
directory of thecom.gupaoedu
project, create them separately
- HelloService.java
- GpImportBeanDefinitionRegistrar.java
- EnableGpRegistrar.java
- TestMain
- Define a class HelloService that needs to be loaded into the IOC container
public class HelloService {
}
- Define a Registrar implementation, define a bean, and load it into the IOC container
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- define an annotation class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
- write a test class
@Configuration
@EnableGpRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class));
}
}
- Through the result demonstration, it can be found that the bean
HelloService
has been loaded into the IOC container.
This is the function implementation of dynamic loading, which has a lot more flexibility than @Configuration configuration injection. ok, go back to the analysis of FeignClient.
FeignClientsRegistrar.registerBeanDefinitions
- Inside the
registerDefaultConfiguration
method, check whether there is@EnableFeignClients
from theSpringBoot
startup class. If there is this annotation, then complete the registration of some configuration content related to theFeign
framework. - Inside the
registerFeignClients
method, scan the class decorated with@FeignClient
fromclasspath
, parse the content of the class intoBeanDefinition
, and finally add the parsedFeignClient BeanDeifinition
to theBeanDefinitionReaderUtils.resgisterBeanDefinition
by callingspring
in the Spring framework
//BeanDefinitionReaderUtils.resgisterBeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
What needs to be analyzed here is theregisterFeignClients
method. This method mainly scans all the@FeignClient
annotations in the classpath, and then performs dynamic bean injection. It will eventually callregisterFeignClient
method.
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerFeignClient(registry, annotationMetadata, attributes);
}
FeignClientsRegistrar.registerFeignClients
The definition of the registerFeignClients method is as follows.
//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//获取@EnableFeignClients注解的元数据
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//获取@EnableFeignClients注解中的clients属性,可以配置@FeignClient声明的类,如果配置了,则需要扫描并加载。
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//默认TypeFilter生效,这种模式会查询出许多不符合你要求的class名
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); //添加包含过滤的属性@FeignClient。
Set<String> basePackages = getBasePackages(metadata); //从@EnableFeignClients注解中获取basePackages配置。
for (String basePackage : basePackages) {
//scanner.findCandidateComponents(basePackage) 扫描basePackage下的@FeignClient注解声明的接口
candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //添加到candidateComponents,也就是候选容器中。
}
}
else {//如果配置了clients,则需要添加到candidateComponets中。
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
//遍历候选容器列表。
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) { //如果属于AnnotatedBeanDefinition实例类型
// verify annotated class is an interface
//得到@FeignClient注解的beanDefinition
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //获取这个bean的注解元数据
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取元数据属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//获取@FeignClient中配置的服务名称。
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
Registration of FeignClient Bean
This method is to register the FeignClient interface with the Spring IOC container,
FeignClient is an interface, so what is the object injected into the IOC container here?
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName(); //获取FeignClient接口的类全路径
Class clazz = ClassUtils.resolveClassName(className, null); //加载这个接口,得到Class实例
//构建ConfigurableBeanFactory,提供BeanFactory的配置能力
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
//获取contextId
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
//构建一个FeignClient FactoryBean,这个是工厂Bean。
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
//设置工厂Bean的相关属性
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//BeanDefinitionBuilder是用来构建BeanDefinition对象的建造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
//把@FeignClient注解配置中的属性设置到FactoryBean中。
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject(); //factoryBean.getObject() ,基于工厂bean创造一个bean实例。
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //设置注入模式,采用类型注入
definition.setLazyInit(true); //设置延迟华
validate(attributes);
//从BeanDefinitionBuilder中构建一个BeanDefinition,它用来描述一个bean的实例定义。
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); //把BeanDefinition的这个bean定义注册到IOC容器。
}
In summary, the code analysis, in fact, the implementation logic is very simple.
- Create a BeanDefinitionBuilder.
- Create a factory bean and set the properties resolved from the @FeignClient annotation to this FactoryBean
- Call registerBeanDefinition to register in the IOC container
About FactoryBean
In the above logic, what we need to focus on is FactoryBean, which you may have less exposure to.
First of all, it should be noted that FactoryBean and BeanFactory are different. FactoryBean is a factory Bean that can generate a Bean instance of a certain type. Its biggest function is to allow us to customize the Bean creation process.
However, BeanFactory is a basic class in the Spring container and is also a very important class. In the BeanFactory, beans in the Spring container can be created and managed.
The following code is the definition of the FactoryBean interface.
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
From the above code, we found that three important characteristics of a Spring Bean are defined in FactoryBean: whether it is a singleton, Bean type, and Bean instance. This should also be our most intuitive feeling about a Bean in Spring.
FactoryBean custom use
Let's simulate the @FeignClient parsing and factory bean construction process.
- First define an interface, which can be analogous to the FeignClient we described above.
public interface IHelloService {
String say();
}
- Next, define a factory bean, which mainly generates dynamic proxy for IHelloService.
public class DefineFactoryBean implements FactoryBean<IHelloService> {
@Override
public IHelloService getObject() {
IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {
System.out.println("begin execute");
return "Hello FactoryBean";
});
return helloService;
}
@Override
public Class<?> getObjectType() {
return IHelloService.class;
}
}
- Dynamically inject Bean instances by implementing the ImportBeanDefinitionRegistrar interface
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
DefineFactoryBean factoryBean=new DefineFactoryBean();
BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(
IHelloService.class,()-> factoryBean.getObject());
BeanDefinition beanDefinition=definition.getBeanDefinition();
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- Declare an annotation to represent the injected import of a dynamic bean.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
- Write a test class to test the dynamic injection of the IHelloService interface
@Configuration
@EnableGpRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
IHelloService is=applicationContext.getBean(IHelloService.class);
System.out.println(is.say());
}
}
- Running the above test method, you can see that the interface IHelloService is dynamically proxied and injected into the IOC container.
FeignClientFactoryBean
As can be seen from the above case, when the Spring IOC container injects FactoryBean, it will call the getObject() method of FactoryBean to obtain an instance of the bean. So we can follow this idea to analyze FeignClientFactoryBean
@Override
public Object getObject() {
return getTarget();
}
The implementation code for constructing the object bean is as follows. The implementation of this code is long. Let's look at it in several steps.
Construction of Feign context
Let's first look at the construction logic of the context, the code part is as follows.
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
}
Two key object descriptions:
- FeignContext is the only context in the world. It inherits NamedContextFactory. It is used to uniformly maintain the isolated context of each feign client in feign. The registration of FeignContext to the container is done on FeignAutoConfiguration. The code is as follows!
//FeignAutoConfiguration.java
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
When initializing FeignContext, configurations
will be put into FeignContext. configuration
represents all @FeignClients currently being scanned.
- Feign.Builder is used to build Feign objects, based on the builder to realize the construction of context information, the code is as follows.
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class)); //contract协议,用来实现模版解析(后面再详细分析)
// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
As can be seen from the code, the feign
method mainly generates Feign context information for different service providers, such as logger
, encoder
, decoder
, etc. Therefore, from this analysis process, it is not difficult for us to guess its principle structure, as shown in the following figure
The implementation of parent-child container isolation is as follows. When the get method is called, the instance object of the specified type will be obtained from context
.
//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + contextId);
}
return instance;
}
Next, call the NamedContextFactory
method in getInstance
.
//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
//根据`name`获取容器上下文
AnnotationConfigApplicationContext context = this.getContext(name);
try {
//再从容器上下文中获取指定类型的bean。
return context.getBean(type);
} catch (NoSuchBeanDefinitionException var5) {
return null;
}
}
getContext
method obtains the context object from the contexts
container according to name
, and if not, calls createContext
to create it.
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);
}
Generate dynamic proxy
In the second part, if 06205e65711a4f is not configured in the url
annotation, that is, the absolute request path is not taken, the following logic is executed.
Since we are using name
in the @FeignClient annotation, we need to execute the branch logic of the load balancing strategy.
<T> T getTarget() {
//省略.....
if (!StringUtils.hasText(url)) { //是@FeignClient中的一个属性,可以定义请求的绝对URL
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name
+ "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
//省略....
}
The code implementation of the loadBalance method is as follows, of which Client
is implemented when Spring Boot is automatically assembling. If other http protocol frameworks are replaced, the client corresponds to the configured protocol api.
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client); //针对当前Feign客户端,设置网络通信的client
//targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon or spring-cloud-starter-loadbalancer?");
}
The code of HystrixTarget.target
is as follows. We do not integrate Hystrix, so the processing logic related to Hystrix will not be triggered.
//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //没有配置Hystrix,则走这部分逻辑
return feign.target(target);
}
//省略....
return feign.target(target);
}
Enter the Feign.target
method, the code is as follows.
//Feign.java
public <T> T target(Target<T> target) {
return this.build().newInstance(target); //target.HardCodedTarget
}
public Feign build() {
//这里会构建一个LoadBalanceClient
Client client = (Client)Capability.enrich(this.client, this.capabilities);
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
//OpenFeign Log配置
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
//模版解析协议(这个接口非常重要:它决定了哪些注解可以标注在接口/接口方法上是有效的,并且提取出有效的信息,组装成为MethodMetadata元信息。)
Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
//封装Request请求的 连接超时=默认10s ,读取超时=默认60
Options options = (Options)Capability.enrich(this.options, this.capabilities);
//编码器
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
//解码器
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
//synchronousMethodHandlerFactory, 同步方法调用处理器(很重要,后续要用到)
Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
//ParseHandlersByName的作用就是我们传入Target(封装了我们的模拟接口,要访问的域名),返回这个接口下的各个方法,对应的执行HTTP请求需要的一系列信息
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
In the build
method, an instance object of ReflectiveFeign
is returned. Let's first look at the ReflectiveFeign
method in newInstance
.
public <T> T newInstance(Target<T> target) { //修饰了@FeignClient注解的接口方法封装成方法处理器,把指定的target进行解析,得到需要处理的方法集合。 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //定义一个用来保存需要处理的方法的集合 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //JDK8以后,接口允许默认方法实现,这里是对默认方法进行封装处理。 List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //遍历@FeignClient接口的所有方法 for (Method method : target.type().getMethods()) { //如果是Object中的方法,则直接跳过 if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) {//如果是默认方法,则把该方法绑定一个DefaultMethodHandler。 DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else {//否则,添加MethodHandler(SynchronousMethodHandler)。 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;}
The above code is actually not difficult to understand.
Parse the methods declared by the @FeignClient interface, and bind different processors according to different methods.
- Default method, bind DefaultMethodHandler
- Remote method, bind SynchronousMethodHandler
- Create a dynamic proxy using the Proxy provided by the JDK
MethodHandler will parse and store method parameters, method return values, parameter sets, request types, and request paths, as shown in the following figure.
FeignClient interface analysis
Interface parsing is also a very important logic in Feign, which can convert the properties declared by the interface into the protocol parameters of HTTP communication.
Execute logic RerlectiveFeign.newInstance
public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //here}
targetToHandlersByName.apply(target); will parse the annotations on the interface method, so as to parse out the specific configuration information of the method granularity, and then produce a SynchronousMethodHandler
Then you need to maintain a map of <method, MethodHandler> and put it into the FeignInvocationHandler implementation of InvocationHandler.
public Map<String, MethodHandler> apply(Target target) { 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;}
In order to better understand the above logic, we can use the following picture to understand!
Periodic summary
Through the above process analysis, the class declared as @FeignClient annotation will eventually generate a dynamic proxy object FeignInvocationHandler when it is injected.
When the method call is triggered, it will be intercepted by FeignInvocationHandler#invoke. What FeignClientFactoryBean does during the instantiation process is shown in the following figure.
To sum up a few points:
- Parse Feign's context configuration, build a container context for the current service instance and return the Feign object
- Feign sets the log, encode, decoder, and other configuration items to the Feign object according to the upper and lower surrounding configurations
- For the target service, use LoadBalance and Hystrix for packaging
- Through the Contract protocol, the declaration of the FeignClient interface is parsed into a MethodHandler
- Traverse the MethodHandler list, and set the
SynchronousMethodHandler
handler for methods that require remote communication to implement synchronous remote calls. - Build dynamic proxy objects using the dynamic proxy mechanism in the JDK.
Remote communication implementation
In the Spring startup process, after all the preparations are ready, the remote call is started.
In the previous analysis, we know that OpenFeign finally returns an object of #ReflectiveFeign.FeignInvocationHandler.
Then when the client initiates a request, it will enter the FeignInvocationHandler.invoke method, which we all know is an implementation of a dynamic proxy.
//FeignInvocationHandler.java@Overridepublic 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(); } return dispatch.get(method).invoke(args);}
Next, in the invoke method, this.dispatch.get(method)).invoke(args)
is called. this.dispatch.get(method)
will return a SynchronousMethodHandler for interception processing.
this.dispatch, which is actually created during the initialization process, is an instance of private final Map<Method, MethodHandler> dispatch;
.
SynchronousMethodHandler.invoke
This method will generate the completed RequestTemplate object according to the parameters, which is the template of the Http request. The code is as follows, and the implementation of the code is as follows:
@Override
public Object invoke(Object[] argv) throws Throwable { //argv,表示调用方法传递的参数。
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;
}
}
}
In the execution process of the above code, a Request object needs to be constructed first, and then the client.execute method is called to execute the network communication request. The overall implementation process is as follows.
executeAndDecode
After the above code, we have assembled the RequestTemplate. There is a executeAndDecode()
method in the above code. This method generates the Request request object through the RequestTemplate, and then uses the Http Client to obtain the response to obtain the response information.
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template); //把template转化为请求报文
if (logLevel != Logger.Level.NONE) { //设置日志level
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//发起请求,此时client是LoadBalanceFeignClient,需要先对服务名称进行解析负载
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;
}
}
LoadBalanceClient
@Overridepublic
Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url()); //获取请求uri,此时的地址还未被解析。
String clientName = asUri.getHost(); //获取host,实际上就是服务名称
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); //加载客户端的配置信息
IClientConfig requestConfig = getClientConfig(options, clientName); //创建负载均衡客户端(FeignLoadBalancer),执行请求
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
} catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
As you can see from the above code, lbClient(clientName) creates a load balancing client, which is actually the generated class as described below:
public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
Overall summary
Analysis of the overall communication principle of OpenFeign, as shown in the following figure.
Copyright notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated. Reprint please indicate from Mic takes you to learn architecture!
If this article is helpful to you, please help to follow and like, your persistence is the driving force for my continuous creation. Welcome to follow the WeChat public account of the same name to get more technical dry goods!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。