目录
- 一、Spring Cloud OpenFeign简单使用
- 1. 简单使用
- 2. FeignClient由服务提供者提供API jar
- order-api
- order-service
- spring-cloud-user-service
- 二、原理分析
Spring Cloud OpenFeign : 声明式的伪RPC调用,可以让服务调用者面向接口进行开发,底层是http通信。
相较与Ribbon的客户端负载均衡,每次请求需要使用RestTemplate进行调用;而OpenFeign是在Ribbon基础上进行封装,将http请求封装为接口类,达到了调用远程接口就像调用内部interface实现一样。
一、Spring Cloud OpenFeign简单使用
1. 简单使用
- 首先添加依赖
spring-cloud-starter-openfeign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
openfeign 可以支持 OKHTTP,对HTTP通信做了很多优化。
- 定义FeignClient
基于接口服务来定义FeignClient:
接口提供者服务的名称:spring-cloud-order-service
提供的接口:@GetMapping("/orders")
所以可以将FeignClient定义为:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
// 服务提供者的服务名称
// 伪RPC
@FeignClient("spring-cloud-order-service")
public interface OrderServiceFeignClient {
@GetMapping("/orders")
String getAllOrder();
}
- Controller内注入该FeignClient接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderFeignController {
@Autowired
OrderServiceFeignClient orderServiceFeignClient;
@GetMapping("/testFeign")
public String testFeign() {
return orderServiceFeignClient.getAllOrder();
}
}
可以直接在Controller内依赖注入OrderServiceFeignClient接口,进行http调用,而不需要再写RestTemplate。
PS: 之前的写法:
@RestController
public class UserController2 {
@Autowired
RestTemplate restTemplate;
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@GetMapping("/user2/{id}")
public String getBuId(@PathVariable("id") int id) {
String rst = restTemplate.getForObject("http://spring-cloud-order-service" + "/orders", String.class);
return rst;
}
}
- SpringBoot启用
@EnableFeignClients
且确保扫描到我们定义的OrderServiceFeignClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@EnableFeignClients
@ComponentScan("com.bigshen")
@SpringBootApplication
public class SpringCloudUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudUserServiceApplication.class, args);
}
}
- 启用OKHttp
Feign底层其实使用的是Http通信,效率不高,如果想提升性能,可以使用OKHTTP:
application.properties内启动配置:
feign.okhttp.enabled=true
feign.httpclient.enabled=false
pom中引入OKhttp依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.0</version>
</dependency>
当传输的数据比较大时,也支持压缩传输等配置:
2. FeignClient由服务提供者提供API jar
一般我们实际使用时,FeignClient会由服务提供者封装为jar包方式的API供服务调用者调用;服务调用者不需要关心FeignClient的定义。
spring-cloud-order-service 订单服务: Maven的多模块项目,包含两个服务提供者API的order-api
模块,以及服务的具体实现order-service
spring-cloud-user-service 用户服务: 单纯的SpringBoot项目
order-api
订单服务: order-api,
OrderService : 定义订单服务提供的接口
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
public interface OrderService {
@GetMapping("/order")
String getAllOrders();
@PostMapping("/order")
String insertOrder(OrderDTO order);
}
OpenFeignClientService :
import com.bigshen.springcloud.demo.OrderService;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient("shen-order-service") // 声明FeignClient
public interface OpenFeignClientService extends OrderService {
}
order-service
订单接口的具体实现模块,依赖于 order-api 模块
import com.bigshen.springcloud.demo.OrderDTO;
import com.bigshen.springcloud.demo.OrderService;
import org.springframework.web.bind.annotation.RestController;
// 发布服务
@RestController
public class OrderServiceImpl implements OrderService {
@Override
public String getAllOrders() {
System.out.println(1);
return "Shen all orders";
}
@Override
public String insertOrder(OrderDTO orderDTO) {
System.out.println(orderDTO);
return "Success insert";
}
}
spring-cloud-user-service
用户服务 : 依赖 order-api 模块
import com.bigshen.springcloud.demo.OrderDTO;
import com.bigshen.springcloud.demo.client.OpenFeignClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
OpenFeignClientService openFeignClientService; // 动态代理,可以直接依赖注入order-api的 OpenFeignClientService,表明该Bean肯定在IOC容器中
@GetMapping("/testFeign001")
public String getAllOrder() {
return openFeignClientService.getAllOrders();
}
@GetMapping("/testFeign002")
public String insertOrder() {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setName("111");
return openFeignClientService.insertOrder(orderDTO);
}
}
Main:指定FeignClien所在的包,进行扫描
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(basePackages = "com.bigshen.springcloud.demo.client") // 扫描API包提供的FeignClient
public class SpringCloudUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudUserServiceApplication.class, args);
}
}
二、原理分析
Feign本身底层是基于Http通信来完成的。
http通信: http//ip:port/requestPath?param1=aa¶m2=bb,
需要确定: ip、port、请求Method get or post or put or delete、参数params;
而根据声明的FeignClient的注解,其实可以确定这些东西的。
那么Feign要做的事情:
- 参数的解析和装载
- 针对指定的FeignClient,生成动态代理
- 针对FeignClient中的方法描述进行解析
- 组装出一个Request对象,发起请求
源码分析路径
源码:
首先需要扫描添加了`@FeignClient`注解的服务,
-> @EnableFeignClients
org.springframework.cloud.openfeign.EnableFeignClients
注解内通过`@Import(FeignClientsRegistrar.class)`指定需要自动装配的Bean
-> FeignClientsRegistrar
org.springframework.cloud.openfeign.FeignClientsRegistrar
FeignClientsRegistrar实现ImportBeanDefinitionRegistrar进行Bean的自动装配
-> org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
-> org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
扫描`@EnableFeignClients`注解指定的baskPackage,得到该路径下添加了`@FeignClient`声明的interface
-> org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
注册FeignClient,将类信息解析为BeanDefinition
```
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
```
这里要注意的是,封装的BeanDefinition类为FeignClientFactoryBean,
-> org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition
将BeanDeinition放入Spring IOC容器,也就是DefaultListableBeanFactory#beanDefinitionMap 中
这里要注意的是,封装的BeanDefinition类为FeignClientFactoryBean,从FeignClientFactoryBean类名可以看出这是一个生产FeignClient工厂的Bean,
且该类实现了`ApplicationContextAware`接口,也是一个Spring的上下文。
FactoryBean的一个特征,当需要获得这个Bean的真正实例,会调用getObject()方法来实现,调用`org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject`
-> org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject
-> org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget
-> org.springframework.cloud.openfeign.DefaultTargeter#target
-> feign.Feign.Builder#target(feign.Target<T>)
-> feign.ReflectiveFeign#newInstance
解析接口声明的 GetMapping、接口路径、入参等
```
// 动态代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);
```
真正创建代理类:
-> feign.InvocationHandlerFactory#create
-> feign.InvocationHandlerFactory.Default#create
-> feign.ReflectiveFeign.FeignInvocationHandler#FeignInvocationHandler
代理类生成后,当进行真正接口调用`xxxFeignClient.method()`时,动态代理会拦截,执行的是代理类的invoke方法:
-> feign.ReflectiveFeign.FeignInvocationHandler#invoke
内部先根据method,从dispatch中获取到对应执行类,根据method进行分发
```
dispatch.get(method).invoke(args);
```
-> feign.SynchronousMethodHandler#invoke
-> feign.SynchronousMethodHandler#executeAndDecode
-> org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
```
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
```
底层会依赖Ribbon的负载均衡,封装请求的request、encoder,接收response、decoder,底层仍是HTTP通信。
本案例源码地址: Spring Cloud OpenFeign OpenFeign官网 : spring -> 头部导航Projects -> spring-cloud -> 左侧导航 Spring Cloud OpenFeign -> Spring Cloud OpenFeign -> 头部导航 LEARN -> Reference Doc.