Spring Cloud OpenFeign - 远程调用

2,588 阅读5分钟

🌈一、什么是Feign?

Feign makes writing java http clients easier,这是官方给出的一个说明,本意翻译是:Feign使编写Java http客户端更容易,Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,Feign可以通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求。

🎉Feign封装了Http调用流程,更适合面向接口化的编程习惯。

在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的远程调用框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Forest、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。

二、💭理解远程调用

  • 本地调用(Local Procedure Call,简称LPC
  • 远程调用(Remote Procedure Call,简称RPC

feign主要是为我们提供了远程调用的服务,那么什么是远程调用呢? 远程调用说白了可以理解为不同服务之间方法的调用,实质上是两台主机间的网络通信 ,涉及到网络通信又必然会有序列化、反序列化,编解码等一些必须要考虑的问题,现在业界内比较流行的一些 RPC 框架,例如:Dubbo提供的是基于接口的远程方法调用,通过rpc远程调用框架,客户端只需要知道接口的定义即可调用远程服务。

远程调用.png

feign主要就是用来简化我们发起远程调用的代码,以一个远程调用Github开放的API为🌰:

/**
 * GitHub客户端GitHubFeign,访问GitHub开发平台API,开放平台API地址:https://www.apifox.cn/apihub/
 *
 * @author: jacklin
 * @date: 2022/6/30 21:20
 */
@FeignClient(name = "github-client", url = "https://api.github.com")
public interface GitHubFeign {


    /**
     * 查找github标准库信息
     * <p>
     * https://api.github.com/search/repositories   v
     *
     * @author: jacklin
     * @since 2022/6/30 21:27
     **/
    @GetMapping(value = "/search/repositories", produces = MediaType.APPLICATION_JSON_VALUE)
    String searchRepositories(@RequestParam("q") String q);
}

第一步: Maven pom文件中引入 OpenFeign 组件。

image.png

第二步:客户端需要定义一个GitHubFeign接口,里面定义一个searchRepositories()方法,可以看到这个接口上添加了@FeignClient注解,而括号里面指定了服务名:github-client,显示声明这个接口是用来远程调用GitHub API服务的,url用来指定调用服务的全路径,其他方法路径前缀必须与url地址一致,完整的请求路径URL地址:https://api.github.com/search/repositories

第三步:需要在服务启动类添加@EnableFeignClients注解,在服务启动时,Spring扫描被@FeignClints修饰的接口,基于动态代理生成本地JDK Proxy代理对象实例,然后将这些代理实例注册到Spring IOC容器中,当远程接口被调用时,由Proxy代理实例去完成真正的远程访问,并返回结果。

第四步:在Controller引入GitHubFeign服务,完成远程服务调用:

@RestController
@RequestMapping(value = "/github", produces = MediaType.APPLICATION_JSON_VALUE)
public class GithubController {

    @Resource
    private GitHubFeign gitHubFeign;

    /**
     * 查找github标准库信息
     *
     * @author: jacklin
     * @since 2022/6/30 21:36
     **/
    @GetMapping(value = "/searchRepositories")
    String searchRepositories(@RequestParam(value = "q") String q) {
        return gitHubFeign.searchRepositories(q);
    }
}

返回结果数据如下👉:

image.png

可以看出,feign使得远程调用跟本地方法是一样,极大的简化了rpc远程调用的方式。

三、OpenFeign和Feign的区别

可以认为OpenFeign是Feign的增强版,不同的是OpenFeign支持Spring MVC注解

FeignOpenFeign
Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。OpenFeign的前身是Neflix Feign,Spring Cloud在Feign的基础上扩展支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

OpenFeignFeign底层都内置了Ribbon负载均衡组件,在导入OpenFeign依赖后无需专门导入Ribbon依赖,用做客户端负载均衡,去调用注册中心服务。

四、OpenFeign核心工作原理

1️⃣ 通过 @EnableFeignCleints触发Spring应用程序对classpath中@FeignClient修饰类的扫描。
2️⃣ 解析到 @FeignClient 修饰类后,Feign框架通过扩展Spring Bean Deifinition的注册逻辑,最终注册一个FeignClientFacotoryBean进入Spring容器
3️⃣ Spring容器在初始化其他用到 @FeignClient 接口的类时, 获得的是FeignClientFacotryBean产生的一个代理对象Proxy
4️⃣ 基于Java原生的动态代理机制,针对Proxy的调用,都会被统一转发给Feign框架所定义的一个 InvocationHandler,由该Handler完成后续的HTTP转换,发送、接收以及HTTP响应的工作。

五、OpenFeign包扫描原理

要想通过OpenFeign实现远程调用,就涉及到一个OpenFeign的核心注解:@EnableFeignClient,根据字面意思可以知道,该注解是开启OpenFeign的功能,一般都会被添加到我们的启动类上。

Spring包扫描的大体流程:

  1. 开启OpenFeign功能@EnableFeignClients,接着通过@Import(FeignClientsRegistrar.class) 这个import的方式导入FeignClientsRegistrar类,开启OpenFeign组件的加载。
@SpringBootApplication
@EnableFeignClients
public class MambaBlockDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MambaBlockDemoApplication.class, args);
    }
}
  1. FeignClientsRegistrar负责Feign接口的加载,源码如下:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   //注册配置
   registerDefaultConfiguration(metadata, registry);
   //注册FeignClient
   registerFeignClients(metadata, registry);
}
  1. registerFeignClients()方法会调用findCandidateComponents()方法来查找指定路径的basePackages下所有被@FeignClients注解修饰的类、接口。
LinkedHashSet<BeanDefinition> candidateComponents = Set<BeanDefinition> findCandidateComponents(String basePackage)
  1. 只保留被@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");

      Map<String, Object> attributes = annotationMetadata
            .getAnnotationAttributes(FeignClient.class.getCanonicalName());

      String name = getClientName(attributes);
      registerClientConfiguration(registry, name, attributes.get("configuration"));
      //注入到Spring容器中
      registerFeignClient(registry, annotationMetadata, attributes);
   }
}

六、总结

本文通过远程调用的GitHub开放API用到的OpenFeign作为示例代码作为入口进行讲解。然后以图解+解读源码的方式深入剖析了OpenFeign的运行机制和架构设计,👨‍🎓我是austin流川枫,欢迎点赞👍+留言💬+关注🧡