Feign的工作原理
openFeign为了实现高度的灵活和舒适的使用体验,使用了大量的设计模式。简直是设计模式学习的最佳范本。
初始化过程
- @EnableFeignClients注解将类FeignClientsRegistrar注册到Spring中。
- 当springboot应用启动时,FeignClientsRegistrar会扫描所有@FeignClients的注解的类,将这些接口bean注册到spring容器中。
- 在spring初始化@FeignClients接口bean时,通过FeignClientFactoryBean生成相关接口的代理类。
- FeignClientFactoryBean生成代理时,Feign会为每个接口方法创建一个RequestTemplate,该对象封装HTTP请求需要的全部信息,如请求参数名,请求方法等。
- 在实际接口调用时,RequestTemplate生成Request,然后把Request交给Client去处理。
运行时调用栈
- ReflectiveFeign 被反射实例化
- 调用ReflectiveFeign.invoke
- 调用SynchronousMethodHandler.invoke。此处实例化RequestTemplate
- 调用SynchronousMethodHandler.executeAndDecode
- 将RequestTemplate build为request,调用http客户端执行
- 将Response Decode为Object并返回
初始化过程重要源代码
在spring-cloud-openfeign-core.jar!/META-INF/spring.factories中,定义了Feign自动配置的相关类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
FeignAutoConfiguration是核心的配置,根据条件分别自动导入了FeignContext, Targeter, feign.Client。这些bean在不同情况下有不同的实现。
Bean注册与初始化
- FeignClientsRegistrar在注册FeignClient接口时,将FeignClientFactoryBean作为构造工厂传给了BeanDefinition,这样后续bean初始化时可以通过FeignClientFactoryBean实现接口。
//FeignClientsRegistrar.java
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
//此次省略若干行
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
- 以上代码中BeanDefinitionBuilder提供了一种动态设置参数的能力。@EnableFeignClients注解的参数值在此时被设置到AbstractBeanDefinition
- 在初始化FeignClient声明的某个接口时,FeignClientFactoryBean将首先被创建一次。由于每个FeignClient接口声明不同的名称,FeignClientFactoryBean会被重复创建。FeignClientFactoryBean创建后将基于上文beanDefinition的PropertyValues被逐一赋值。
- bean容器中的接口实例是通过FeignClientFactoryBean.getObject() -> getTarget()获得的。
- 在FeignClientFactoryBean.getTarget()中,Feign.Builder是一个创建Feign的构造器,提供了一系列创建代理类的模板,Client通用配置都在这个builder中。
//FeignClientFactoryBean.java
<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));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
- FeignClientFactoryBean.getTarget()将具体的代理创建工作交给了Targeter.target();
bean初始化相关的类如下:
- ReflectiveFeign.newInstance()创建具体的代理类,指定FeignInvocationHandler执行接口响应。
至此,Feign的初始化过程结束。
http请求实现逻辑
当一个服务ServiceCaller调用FeignClient声明的方法myMathod时,将触发如下调用链:
ServiceCaller
1. -> $Proxy.myMathod(param)
2. -> ReflectiveFeign$FeignInvocationHandler.invoke(Object,Method,Object[])
3. -> SynchronousMethodHandler.invoke(Object[])
4. -> SynchronousMethodHandler.executeAndDecode(RequestTemplate)
5. -> CloseableHttpClient.execute(HttpUriRequest)
- 第1步是JDK实现的接口代理实现类,方法名就是我们声明的方法名。
- FeignInvocationHandler统一响应了接口所有的方法,通过Method实现了对具体的响应方法的分发。
- 第3步调用RequestTemplate.Factory将请求参数封装到RequesTemplate中。
- 第4步是http请求的核心,包含了我们手写http请求执行的常规动作:
- 执行RequestInterceptor拦截器,对请求进行修饰;
- 执行Target.apply()将RequestTemplate转换为Request;
- 执行feign.Client.execute(request, options);即上面的第5步;
- 将Response转换成接口声明的返回参数格式;
- 如果有异常,执行异常处理流程decode404或者ErrorDecoder;
设计模式研读
适配器模式
Feign支持 URLConnection、HTTP Client 和 OKHttp实现远程调用,Feign通过适配器模式同时支持这些远程调用库。
先看一下经典的适配器模式UML图:
通过抽象出feign.Client接口实现具体的http调用方案的解耦。通过ApacheHttpClient等对象,实现具体http请求方案的适配。
装饰器模式
Feign通过Ribbon实现了客户端负载均衡,负载均衡是http请求的一个增强功能,非常适合通过装饰器模式实现。
经典的装饰器模式UML图:
依然还是Client接口,不同的是LoadBalancerFeignClient除了实现了Client外,还拥有一个Client的属性,LoadBalancerFeignClient在执行http请求时,除了delegate执行具体的http请求以外,还提供客户端负载均衡调度功能。
代理模式
为了实现@FeignClient注解的接口,ReflectiveFeign直接使用了JDK的动态代理。
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
ReflectiveFeign类图:
适配器模式与装饰器模式的区别
装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一一样。适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。而装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。
适配器模式与代理模式的区别
装饰器模式关注于在一个对象上动态的添加业务逻辑,然而代理模式关注于控制对对象的访问。换句话说,用代理模式在于对它的调用方客户屏蔽一个业务逻辑的具体实现信息。因此,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。
构造器模式(Builder)