写于:2019-06-23 22:52:37
参考资料:
Spring Cloud Feign 默认使用 HTTP 协议进行远程服务调用。
一个简单的案例,假设此时有一个服务provider 和一个服务consumer。
| 服务provider | 9527 | 提供接口/hello/{name} |
| 服务consumer | 9528 | 调用 服务provider的 hello/{name} 接口 |
服务consumer 通过 RestTemplate 发起 HTTP 请求调用服务 provider 接口代码如下:
@RequestMapping("/feign/hello/{param}")public Object hello(@PathVariable String param,) throws InterruptedException {return restTemplate.getForObject("http://localhost:9527/hello/" + param,String.class);}
在上面代码中,我们通过传入一个字符参数,发起 http 请求,接收一个字符串的返回结果。
假设,此时我们需要传入的是一个对象,对象中存在多个参数,返回结果同样也是一个对象。
这时候,为了能够提高开发效率,我们就需要对 RestTemplate 进行封装,例如,通过反射获取参数对象值,并进行拼接,返回结果同样需要通过反射进行赋值等。
而 Feign 帮我们对 RestTemplate 进行了封装,除此之外,Feign 集成了 Hystrix ,Ribbon 等功能。
一、Feign 简单使用
step1、引入依赖
step2、开启 Feign 功能
@EnableFeignClients
step3、编写相关代码,如下图
总结:使用 Feign 进行远程调用,只需定义一个 Feign 客户端,通过调用方法的形式,便可完成远程接口调用。
二、Feign Client 如何被加载到 Spring 中
在上面 Feign 调用的案例中 HelloClient 只需 @FeignClient 注解,然后 @Autowired 进行注入便可完成 Feign Client 注入。
能被注入,表示 HelloClient 被实例化并注入到 IOC 容器中,那么 Feign Client 又是如何被加载注册并实例化到 IOC 容器中的?
2.1、Feign Client BeanDefinition 加载
2.1.1、入口:@EnableFeignClients
(Spring Boot 2.1.5 、 spring cloud Greenwich.SR1 对应 spring-cloud-openfeign 2.1.1 )
以 @EnableFeignClients 为入口,进行源码分析,相关代码如下:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients {}
通过 @EnableFeignClients 源码查看,聚焦 @Import(FeignClientsRegistrar.class) 中的 FeignClientsRegistrar.class。
2.1.2、FeignClientsRegistrar
类结构图如下:
小贴士:实现了
ImportBeanDefinitionRegistrar的类,会根据该类的配置属性进行相关 Bean 的动态注入。
聚焦 FeignClientsRegistrar#registerBeanDefinitions 方法。
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 注册 EnableFeignClients 中配置的相关到 BeanDefinitionRegistry 中registerDefaultConfiguration(metadata, registry);// 注册 Feign 客户端到 BeanDefinitionRegistry 中registerFeignClients(metadata, registry);}}
方法中调用了两个方法
- registerDefaultConfiguration(metadata, registry);
该方法主要是获取注解 @EnableFeignClients 中是否有相关的配置信息,如果有进行配置信息的注册 - registerFeignClients(metadata, registry);
该方法是用来将我们定义的带有 @FeignClient 注解的类注册到 BeanDefinitionRegistry 中
我们关注的是 Feign Client 的加载,追踪主线方法 registerFeignClients
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 定义过滤条件,需要包含 FeignClientAnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);scanner.addIncludeFilter(annotationTypeFilter);// 获取扫描的包basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// 根据包路径,过滤条件,获取相关类的 BeanDefinitionSet<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// 如果每个 FeignClient 有相关的配置,则进行配置注入registerClientConfiguration(registry, name,attributes.get("configuration"));// 进行 Feign Client 注入registerFeignClient(registry, annotationMetadata, attributes);}}}}}
上述代码,处理逻辑
1、获取所有需要扫描的包路径。
该路径可以从 EnableFeignClients 中的配置中获取,如:value,basePackages,basePackageClasses。
如果 EnableFeignClients 中没有进行配置,默认扫描包路径为:应用 @EnableFeignClients 的类,也就是 Application 启动类所在包路径为主。
例如:com.qguofeng.Application.class 启动类 中 @EnableFeignClients 中没有相关扫描包配置时,则默认扫描 FeignClient 的路径为:com.qguofeng
2、增加扫描条件
在 步骤 1 中确定了需要扫描的包路径,为了获取精确数据,增加筛选条件: 含有 FeignClient.class 注解的类
3、根据 步骤1 确定的 扫描路径,步骤二确定的过滤条件,获取含有注解 FeignClient 的所有的相关类的 Set 集合
4、遍历步骤三获取到的 set 集合,如果 FeignClient 相关类注解 @FeignClient 中含有相关的配置类,则进行配置的注入。然后进行类的注入。
上述流程,完成了 Feign Client 加载注册到了 Spring 中。
扩展: Feign Client 注册的时候,注册了哪些信息。
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {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);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.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);}}
2.1.3、小结

以 @EnableFeignClients 为入口,我们能够得知,在 FeignClientsRegistrar 中,通过扫描相关包路径下的包含注解 @FeignClient 的 Feign Client 的 BeanDefinition 注册到 Spring 中。
2.2、Feign Client 实例化
通过上面的分析, Feign Client 类相关信息 BeanDefinition 被注册到了 Spring 中,此时 Feign Client 并没有被实例化,Feign Client 真正进行实例化是在 @Autowired 时,才进行的实例化。
在之前文章中我们提到得依赖注入:《IOC/DI》 ,Spring 在进行依赖注入时会有进行 bean 的实例化操作。
2.2.1、知识点回顾-AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactoryimplements AutowireCapableBeanFactory {protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {......// 实例化对象instanceWrapper = createBeanInstance(beanName, mbd, args);// 依赖注入populateBean(beanName, mbd, instanceWrapper);......}}
而从该方法开始,进行实例化对象获取的路径如下:
上面路径中,最终会调用 org.springframework.beans.factory.FactoryBean#getObject() 获取到实例对象。
Spring 中 Bean 的获取通常都是需要从相对应的工厂类中进行获取,Feign 也不例外。Feign 实例对象获取的工厂类为 FeignClientFactoryBean 。
2.2.2\聚焦 FeignClientFactoryBean#getObject()
class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware {@Overridepublic Object getObject() throws Exception {return getTarget();}<T> T getTarget() {// Feign 客户端上下文FeignContext context = this.applicationContext.getBean(FeignContext.class);// Feign 客户端构建起Feign.Builder builder = feign(context);// 进行 Feign 请求前缀的拼接// 如果有配置 url 使用 url,如果没有配置使用 name (相当于服务名称)if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();// 获取Feign 客户端 实例对象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 unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}builder.client(client);}// 获取Feign 客户端 实例对象Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));}}
FeignClientFactoryBean 获取实例化对象有个方法,如下:
- return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));
- return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));
不过这两种返回调用最终都是调用的 Targeter#targeter.target(......) 方法。
通过上面的代码
FeignContext context = this.applicationContext.getBean(FeignContext.class);Targeter targeter = get(context, Targeter.class);
能够知道 Targeter 对象直接是从 Spring 上下文中获取到的。
扩展:那么究竟 Targeter 是在什么时候加载到 Spring 上下文中的? 查看
Targeter发现其有两个实现类: DefaultTargeter 和 HystrixTargeter 聚焦这个两个类的配置文件FeignAutoConfiguration,相关配置如下:DefaultTargeter 和 HystrixTargeter 究竟以哪个为主取决于
feign.hystrix.HystrixFeign这个类,也就是说项目中是否引用了feign-hystrix依赖
这里以 Targeter 的实现类 DefaultTargeter 进行展开,UML 如下,
最终调用 ⑤ ReflectiveFeign#newInstance() 获取 Feign Client 实例对象,直接上代码
public class ReflectiveFeign extends Feign {@Overridepublic <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>();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)));}}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;}}
这里主线代码很直观,对 HardCodedTarget<>(this.type, this.name, url) 进行代理,生成一个新的代理对象。
2.2.3、小结
以 @Autowired 为入口,加上 Feign 的自动配置 FeignAutoConfiguration 。可以知道:
Feign Client 在依赖注入时,对 Feign Client 进行实例化操作。实例化对象从 FeignClientFactoryBean 工厂中获取,获取到 HardCodedTarget<>(this.type, this.name, url) 该实例对象被 ReflectiveFeign.FeignInvocationHandler 的代理对象。
2.3、Feign Client 调用流程
在上面的流程中,Feign Client 生成了一个 被 ReflectiveFeign.FeignInvocationHandler 代理的 HardCodedTarget 对象,对象信息如下
所以在使用 Feign Client 进行调用的时候,方法会进入到 ReflectiveFeign.FeignInvocationHandler 中。
ReflectiveFeign.FeignInvocationHandler 是一个 JDK 动态代理类。
2.3.1、代码追踪 UML
2.3.2、② ReflectiveFeign.FeignInvocationHandler#invoke() 方法
public class ReflectiveFeign extends Feign {private final Map<Method, MethodHandler> dispatch;static class FeignInvocationHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {......return dispatch.get(method).invoke(args);}}}
dispatch.get(method) 拿到的是一个 SynchronousMethodHandler 。
SynchronousMethodHandler 中包含了 Feign Client 被代理的对象 HardCodedTarget。如下图:
2.3.4、③ SynchronousMethodHandler#invoke() 方法
final class SynchronousMethodHandler implements MethodHandler {@Overridepublic Object invoke(Object[] argv) throws Throwable {......// 拼接简单的请求RequestTemplate template = buildTemplateFromArgs.create(argv);// 执行请求return executeAndDecode(template);......}}
代码逻辑:
将请求参数和请求路径封装成 RequestTemplate ,然后调用 SynchronousMethodHandler#executeAndDecode()方法执行
2.3.5、④ SynchronousMethodHandler#executeAndDecode 方法
final class SynchronousMethodHandler implements MethodHandler {Object executeAndDecode(RequestTemplate template) throws Throwable {// 拼装完整的 request 请求Request request = targetRequest(template);// 发起请求,并获取返回结果 Responseresponse = client.execute(request, options);// 解析 response 请求,获取最后的结果Object result = decode(response);// 响应返回结果return result;}}
代码逻辑
拼接完整的 request请求方法 targetRequest(template); 最终会调用 HardCodedTarget#apply() 。HardCodedTarget#apply() 组合封装了完整的 request请求。request请求信息如下:
上面就是 Feign Client 调用远程服务,并获取到响应数据的主线流程。
三、总结
3.1、Feign Client 注册到 SPring 中
Spring 根据 扫描包中 使用了注解 @FeignClient 的 Feign Client 。将 Feign Client 信息抽象成为 BeanDefinition 并注册到 Spring 中。
3.2、Feign Client 实例化
在使用 @Autowired 注入 Feign Client 时,触发依赖注入,根据 Feign Client 的 BeanDefinition 信息,实例化 Feign Client ,并生成代理对象, 并放入 IOC 容器中。
3.3、封装 RequestTempalte ,发起 HTTP 请求
执行调用是,调用 Feign Client 的代理对象 ReflectiveFeign.FeignInvocationHandler 的 invoke() 方法。该方法经过一系列调用拼装 RequestTemplate 请求对象,并发送请求调用,获取响应。
DefaultTargeter 和 HystrixTargeter 究竟以哪个为主取决于 
