四、Spring Cloud OpenFeign 伪RPC调用

官网地址 spring-cloud-openfeign

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&param2=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.

posted @ 2019-09-04 22:35  BigShen  阅读(1056)  评论(0编辑  收藏  举报