SpringCloud组件02---OpenFeign实践

OpenFeign简介#

Feign已经不再更新,进入维护状态。SpringCloud版本 OpenFeign作为后起之秀接替他。

Feign是声明性Web服务客户端。 它使编写Web服务客户端更加容易。 要使用Feign,请创建一个接口并对其进行注释。 它具有可插入的注释支持,包括Feign注释和JAX-RS注释。 Feign还支持可插拔编码器和解码器。 Spring Cloud添加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的相同HttpMessageConverters。 Spring Cloud集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。

https://github.com/OpenFeign/feign

https://cloud.spring.io/spring-cloud-openfeign/2.2.x/reference/html/

Ribbon和Feign的区别#

Ribbon#

是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。主启动类上使用@RibbonClient开启
它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟http请求,步骤相当繁琐。

Ribbon 可以用来做客户端负载均衡,调用注册中心的服务

Ribbon的使用需要代码里手动调用目标服务,请参考官方示例:https://github.com/Netflix/ribbon

Feign#

Feign 是在 Ribbon的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。

采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,就能完成对服务方的接口绑定。只需要将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。

然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。Feign自动封装底层Http请求。

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端

Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。

Feign支持的注解和用法请参考官方文档:https://github.com/OpenFeign/feign

Feign本身不支持Spring MVC的注解,它有一套自己的注解

OpenFeign#

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

image-20200602124753981

实践案例#

  • 主启动类开启@EnableFeignClients
  • 写服务接口,对应提供方的Controller,接口上@FeignClient(服务名)@Component实例化。
  • 把这个service接口注入到业务中即可使用。(这个Service会有springcloud动态代理生成实现并实例化)

Server端:#

pom新增open-feign支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<dependencies>
<!--open feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--上面的eureka-client会自动引入ribbon-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<!-- </dependency>-->
<!--业务common类,引用当前项目的版本号-->
<dependency>
<groupId>com.sam.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

<!--MVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!--TEST-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>


<!--DEV-TOOLS-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>

主启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@EnableFeignClients // 开启feign功能
public class CustomerOpenFeign8084 {

/**
* 依赖Eureka7001\7002,Peyment服务方8001\8002四个服务
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(CustomerOpenFeign8084.class,args);
}
}

Service接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 使用OpenFeign调用Server只用一个接口,Feign会使用代理模式给我们生成一个实现。
*/
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE") // server端的服务名称
public interface PaymengFeignService {


@PostMapping(value="/payment/add") // server端的服务地址
public CommonResult<Payment> addPayment(@RequestBody Payment payment);

@GetMapping(value = "/payment/get/{id}") // server端的服务地址
public CommonResult<Payment> getPaymentById(@PathVariable("id") long id);
}

image-20200602132939630

提供方#

controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Controller
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;

@Value("${server.port}")
private String serverPort;

@Resource
private DiscoveryClient discoveryClient;

/**
* 因为是接受调用的server,这里使用@RequestBody接受json,否则字段会丢失
* http://localhost:8002/payment/add
* {"serial":"lalala"} content-type: application/json才行
* @param payment
* @return
*/
@PostMapping(value="/add")
@ResponseBody
public CommonResult<Payment> addPayment(@RequestBody Payment payment){
int result = paymentService.add(payment);
log.info("---------插入:"+result);
if(result > 0){
return CommonResult.success("插入payment成功!serverPort="+serverPort);
}else{
return CommonResult.businessFail("插入payment失败");
}
}

@GetMapping(value = "/get/{id}")
@ResponseBody
public CommonResult<Payment> getPaymentById(@PathVariable("id") long id){
Payment payment = paymentService.getById(id);
if(payment != null){
return CommonResult.success("ok,serverPort="+serverPort, payment);
}else{
return CommonResult.businessFail("查询失败!");
}
}

}

feign常见配置#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
feign:
client:
config:
feignName: # 这里是可变的,如default
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract

超时控制#

因为feign底层是使用的ribbon,调用方的application.yml中配置ribbon的超时时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 超时配置,以下二选一
#ribbon:
# ReadTimeout: 5000 # 连接建立后请求数据的时间
# ConnectTimeout: 5000 # 建立连接的时间

# 或者使用feign原生配置,https://cloud.spring.io/spring-cloud-openfeign/2.2.x/reference/html/
feign:
client:
config:
default: # 可变的name
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic

Feign日志控制#

yml对应包打开日志

1
2
3
logging:
level:
com.sam.springcloud.service.PaymengFeignService: debug

feign配置日志级别,使用上面的yml配置文件控制,或者下面人肉注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import feign.Logger;
@Configuration
public class FeignConfig {

/**
* 可选,设置Feign的日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

Feign更多#

Encoder、Decoder、和Hystyix结合,消息压缩,见

https://cloud.spring.io/spring-cloud-openfeign/2.2.x/reference/html/#spring-cloud-feign-hystrix