第四章 Spring Cloud OpenFeign
在第二章中,我们通过RestTemplate实现了远程调用:
@Autowired
private DiscoveryClient discoveryClient;
private String getLoadBalancedServerAddress() {
List<ServiceInstance> instances = discoveryClient.getInstances("depart-provider");
Collections.shuffle(instances);
String URL = instances.get(0).getUri().toString() + "/provider/depart";
System.out.println(URL);
return URL;
}
@PostMapping
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {
ResponseResult<Boolean> responseResult =
restTemplate.postForObject(getLoadBalancedServerAddress(), depart, ResponseResult.class);
return responseResult;
}
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
String url = getLoadBalancedServerAddress() + "/" + id;
ResponseResult<Depart> result = restTemplate.getForObject(url, ResponseResult.class, id);
return result;
}
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
String url = getLoadBalancedServerAddress() + "/list";
ResponseResult<List<Depart>> result = restTemplate.getForObject(url, ResponseResult.class);
return result;
}
但是这种调用方式,与本地方法调用差异太大,编程的体验也不统一。我们必须改变这种远程调用的开发方式。
目的是:让远程调用像本地方法调用一样简单,此时就需要使用OpenFeign。
远程调用的关键点:服务名称、请求方式和路径、返回值类型、请求参数
OpenFeign简介
OpenFeign 是一个声明式的http客户端,是Spring Cloud在Eureka公司开源的Feign基础上改造而来的,主要作用就是基于注解发送HTTP请求
Features : Declarative REST Client(声明式客户端): Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations
-
JAX-RS(Java Api eXtensions of Restful web Services):对于RESTful的web服务的Java扩展API,由JavaEE6引入。主要就是使用注解简化客户端的开发和部署。
-
Feign:假装、伪装。OpenFeign将服务提供者的Restful服务伪装为接口进行调用,消费者只需要使用Feign接口 + 注解(MVC/JAX-RS)的方式即可为直接调用提供者提供的RESTful服务,无需再使用RestTemplate
-
OpenFeign仅仅是一个伪客户端,不会对请求和响应做额外处理。
OpenFeign与Ribbon
OpenFeign具有负载均衡功能。可以对指定的微服务采用负载均衡的方式进行消费、访问。老版本Spring Cloud所集成的OpenFeign默认采用了Ribbon负载均衡器。由于Netflix已不再维护Ribbon,所以Spring Cloud 2021.x开始集成的 OpenFeign 已经弃用 Ribbon ,而是采用Spring Cloud 自行研发的 Spring Cloud Loadbalancer 作为负载均衡器
使用OpenFeign
参照官网引入依赖:
To include Feign in your project use the starter with group org.springframework.cloud
and artifact id spring-cloud-starter-openfeign
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
服务使用者
在服务调用的启动类上添加注解 @EnableFeignClients,扫描用@FeignClient("service-name")声明的Feign客户端接口并生成代理对象
@FeignClient声明客户端
@FeignClient("depart-provider")
public interface DepartClient {
@PostMapping("/provider/depart")
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/provider/depart/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/provider/depart/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/provider/depart/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/provider/depart/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
-
@FeignClient("depart-provider"):声明服务名称,自动获取服务提供者列表
-
@GetMapping("/provider/depart/list"):声明请求路径和方式,注意请求路径要带上提供者@RequestMapping的路径
-
ResponseResult<List<Depart>>:声明返回值类型
-
方法入参:声明请求参数
OpenFeign将服务提供者的RESTful服务伪装为了接口,以上的信息是根据服务提供者的接口信息获取的。
我们只需要调用这个方法就可以完成远程调用,OpenFeign完成了服务拉取、负载均衡、发送请求获取响应的全部操作。
服务调用者:
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
@Autowired
private DepartClient departClient;
@PostMapping
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {
return departClient.postDepart(depart);
}
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
return departClient.getHandler(id);
}
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
return departClient.listHandler();
}
}
改进FeignClient
在当前的FeignClient中:
```java
@FeignClient("depart-provider")
public interface DepartClient {
@PostMapping("/provider/depart")
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/provider/depart/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/provider/depart/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/provider/depart/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/provider/depart/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
可以使用@RequestMappring或者指定@FeignClient的path属性:
@FeignClient("depart-provider")
@RequestMapping("/provider/depart")
public interface DepartClient {
@PostMapping
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
但是新版Spring Cloud不能这样声明,需要使用第二种方式:
@FeignClient(value = "depart-provider",path = "/provider/depart")
public interface DepartClient {
@PostMapping
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
OpenFeign常用配置
超时时间
spring-cloud-feign-overriding-defaults
spring:
cloud:
openfeign:
client:
config:
default:
#连接超时
connectTimeout: 5000
#读超时
readTimeout: 5000
loggerLevel: basic
-
connectTimeout 连接超时:consumer连接到provider的时间阈值,主要由网络环境影响
-
readTimeout 读超时:连接成功到接收到响应的时间阈值,主要由提供者业务处理逻辑影响
直接覆盖default会对当前服务中所有的Feign客户端都起作用
配置指定Feign客户端的超时时间:
spring:
cloud:
openfeign:
client:
config:
#Feign客户端的名称,depart-provider
feignName:
url: http://remote-service.com
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
responseInterceptor: com.example.BazResponseInterceptor
dismiss404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
micrometer.enabled: false
局部的配置会覆盖全局的配置:
spring:
application:
name: depart-consumer
cloud:
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
openfeign:
client:
config:
default:
connect-timeout: 1
read-timeout: 1
depart-provider:
read-timeout: 10
请求响应压缩
spring-cloud-feign-inheritance
spring:
cloud:
openfeign:
client:
config:
# 全局超时时间
default:
connect-timeout: 1
read-timeout: 1
# 指定客户端超时时间
depart-provider:
read-timeout: 10
# 请求响应压缩
compression:
response:
enabled: true
request:
enabled: true
# 指定对哪种类型进行压缩
mime-types: ["text/xml", "application/xml", "application/json","video/mp4"]
# 指定触发请求压缩的请求最小大小
min-request-size: 1024
-
spring.cloud.openfign.compression.response:对响应结果进行压缩
-
spring.cloud.openfign.compression.request.mime-types:对哪些类型进行压缩,该属性有默认值:
-
spring.cloud.openfign.compression.request.min-request-size:请求大小达到多少触发压缩,该属性有默认值:
@ConfigurationProperties("spring.cloud.openfeign.compression.request")
public class FeignClientEncodingProperties {
private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"};
private int minRequestSize = 2048;
}
底层技术选型
HttpClient4/5
Feign的远程调用底层实现采用的是JDK自带的URLConnection,同时还支持HttpClient和OkHttp。
但是URLConnection不支持连接池,通信效率低,生产中是不会使用这个默认实现的。所以Spring Cloud OpenFeign直接将默认实现变为了HttpClient,同时也支持OkHttp。
# 默认值就是true
spring.cloud.openfeign.httpclient.enable
HttpClient和OkHttp都支持连接池,区别是:在单例模式下HttpClient性能更好,在非单例模式下OkHttp性能更好。
生产环境下一般都是单例的,所以OpenFeign默认使用的就是HttpClient4,在[官网](Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.)中提到:
Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.|
建议更换为httpclient5(需要添加依赖):
spring.cloud.openfeign.httpclient.hc5.enable=true
自定义Http客户端
官网中描述:You can customize the HTTP client used by providing a bean of either org.apache.hc.client5.http.impl.classic.CloseableHttpClient
when using Apache HC5.
在使用HttpClient5的时候可以通过提供一个实现了CloseableHttpClient的bean来自定义HttpClient客户端。
HttpClient代码实现比OkHttp复杂,Spring Cloud OpenFeign仅支持自定义HttpClient,不支持自定义OkHttp。
OkHttp
pom
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
配置
spring.cloud.openfeign.okhttp.enable=true
负载均衡
官网中描述:
Client
feignClient: If Spring Cloud LoadBalancer is on the classpath, FeignBlockingLoadBalancerClient
is used. If none of them is on the classpath, the default feign client is used.
如果classpath中有Spring Cloud LoadBalancer,使用FeignBlockingLoadBalancerClient
进行负载均衡,如果不存在,只使用默认client
开启两个provider:8087和8088,consumer中已经有load balancer依赖,经过测试默认的负载均衡策略就是轮询
老版本使用Ribbon更换负载均衡策略很简单,但是使用LoadBalancer更换负载均衡策略就需要额外定义一个配置类:
public class LoadBalancerConfig {
/**
* @param env 环境对象,获取微服务名称
* @param factory
* @return ReactorLoadBalancer<ServiceInstance> 泛型 <ServiceInstance> 指定对ServiceInstance进行负载均衡
*/
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment env, LoadBalancerClientFactory factory) {
//获取负载均衡客户端名称,也就是provider的服务名称
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
/*
参数一:服务提供者列表
参数二:当前需要的provider微服务名称
作用:从所有服务提供者中获取到指定名称的提供者列表,进行随机选择负载均衡
factory.getLazyProvider(name, ServiceInstanceListSupplier.class):获取指定名称的所有服务提供者列表
*/
return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);
}
}
不需要添加@Confirguation注解,需要在启动类上使用@LoadBalancerClients指定这个配置类:
@LoadBalancerClients(defaultConfiguration = {LoadBalancerConfig.class})
@EnableFeignClients
@SpringBootApplication
public class Consumer03Application {
public static void main(String[] args) {
SpringApplication.run(Consumer03Application.class, args);
}
}
负载均衡策略实际上就是ReactorLoadBalancer接口的实现类,Spring Cloud LoadBalancer只提供了两个:
- RandomLoadBalancer:随机负载均衡
- RoundRobinLoadBalancer:轮询负载均衡(默认)
Spring Cloud LoadBalancer提供的太少,并且更换起来非常麻烦,所以实际开发中不使用Load Balancer,使用Dubbo。
日志
OpenFeign只会在FeignClient所在的包对应的日志级别为DEBUG时输出日志,OpenFeign对应的日志级别有四个:
- NONE:不记录任何日志信息,这是默认值
- BASIC:只记录请求的方法、URL、响应状态码、执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的信息
- FULL:记录请求和响应的明细,包含头信息、请求体、元数据
未指定时FeignClient的默认级别就是NONE,不会输出日志信息
定义日志级别
- 定义项目的日志级别(也就是FeignClient所在包的日志级别)
logging:
level:
com.euneir: debug
- 定义OpenFeign的日志级别
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
不需要加@Confirguation
配置日志生效
- 局部配置:针对指定的FeignClient生效
@FeignClient(value = "depart-provider" ,path = "/provider/depart",configuration = DefaultFeignConfig.class)
public interface DepartClient {
@PostMapping
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
- 全局配置:对扫描到所有的FeignClient生效
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
@SpringBootApplication
public class Consumer03Application {
public static void main(String[] args) {
SpringApplication.run(Consumer03Application.class, args);
}
}
标签:调用,depart,OpenFeign,ResponseResult,id,provider,public,第四章
From: https://www.cnblogs.com/euneirophran/p/18073897