一、OpenFeign简介
OpenFeign是一个声明式的Web服务客户端,它简化了与HTTP API的通信。它的底层原理主要基于Java的反射和动态代理,并且通过利用Spring AOP 框架、RestTemplate、Ribbon 和 Hystrix 等组件,将复杂的 HTTP 调用封装起来,使得开发者能够像调用本地服务一样使用远程服务。
二、OpenFeign 使用步骤
假设有个下单服务为orderService需要调用库存服务stockService中的接口查询商品的库存量。
- 首先在两个服务的pom.xml文件中引入OpenFeign 组件。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 先在orderService创建一个查询商品库存的接口,这个接口上面使用了一个
@FeignClient
注解标注,注解里面的name属性指定了要调用的服务名,表明该远程接口属于哪个服务。接口里面的方法上面使用了@RequestMapping
注解标注,该注解里面指定了要访问的接口路径。
@FeignClient(name = "stock-service")
public interface StockServiceFeign {
/**
* 获取商品信息
*
*/
@RequestMapping("/stock/getStockInfo")
CommonResult<Page<SysRole>> getStockInfo(@RequestParam String stockId);
}
- 在orderService的启动类上添加
@EnableFeignClients
注解,该注解表明在服务启动后开启远程调用。
@EnableFeignClients
@SpringBootApplication
public class NjhGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(NjhGatewayApplication.class, args);
}
}
- 最后到stockService服务中定义一个接口去实现具体的功能,并保证接口地址以及入参与orderService刚定义的接口一致。
@RestController
public class StockController {
@Autowired StockService;
@RequestMapping("/stock/getStockInfo")
public R getStockInfo(@RequestParam String stockId) {
return StockService.getStockInfo(stockId);
}
}
三、实现原理
从上面例子可以看出OpenFeign 使用起来还是很简单方便,但是其底层原理就没那么简单了。其核心的处理逻辑就是使用Spring的AOP动态代理技术,对所有被 @FeignClient 注解修饰的接口生成一个动态代理类,在这个代理类中会将注解中的服务的名称、接口类型、访问路径转化成一个远程服务调用的 Request请求,并发送给目标服务。转化后的Request请求格式类似GET http://stock-service/stock/getStockInfo/10001 HTTP/1.1
,下面看下主要的处理流程:
- 创建代理对象:当你定义一个使用了 @FeignClient 注解的接口时,OpenFeign 会在运行时为这个接口创建一个代理对象。这个代理对象用于处理对远程服务的调用。
- 解析注解:在运行时,OpenFeign 会解析 @FeignClient 注解,确定要连接的服务名称。如果使 用了服务注册中心(如 Eureka),服务名称会被用来查找服务实例。
- 路由匹配:对于每个接口方法,OpenFeign 会创建一个映射,将方法路径(如果提供的话)和 HTTP 请求方法(如 GET、POST 等)与服务端的 API 路径进行匹配。
- 构建请求:当代理对象被调用时,OpenFeign 会构建一个 HTTP 请求,这个请求包含了方法参数作为请求体或查询参数。编码器将这些参数转换为适用于 HTTP 传输的格式。
- HTTP 调用:使用默认的或自定义的 HTTP 客户端,OpenFeign 发送 HTTP 请求到远程服务。如果使用了 Ribbon,它会进行负载均衡来决定请求发送到哪个服务实例。
- 接收响应:HTTP 客户端接收远程服务的响应,并将其传递给解码器。
- 解码响应:解码器将 HTTP 响应体解码为方法可以接受的参数或返回值。如果响应是一个 JSON 或 XML 字符串,解码器会将其转换为 Java 对象。
- 处理异常:如果在请求过程中发生错误,例如连接失败或解码错误,OpenFeign 会将异常传递给调用者。开发者可以定义错误处理策略来处理这些异常。
- 返回结果:最终,解码后的结果会作为方法的返回值返回给调用方。
四、源码解析
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
(1)@EnableFeignClients
注解中的basePackages
属性来设置包扫描的范围,从中找出所有带有@FeignClient
注解的类、接口,并将它们注册为Feign客户端。
@EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign")
(2)进入@EnableFeignClients
注解可以看到里面使用了一个@Import({FeignClientsRegistrar.class})
注解,里面还导入了FeignClientsRegistrar.class
类,主要负责将Feign Client的Bean注册到Spring的IOC容器中。
(3)registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
方法主要用于注册Bean定义。
(4)getBasePackages(AnnotationMetadata importingClassMetadata)
方法将扫描到的所有@FeignClient
注解的元数据,从这些元数据中提取出基础包名,并将它们作为一个Set集合返回。