OpenFeign服务调用
SpringCloud
github官网:https://github.com/spring-cloud/spring-cloud-openfeign
Feign是一个声明式的Web Service客户端。它的出现使开发Web Service客户端变得很简单。使用Feign只需要创建一个接口加上对应的注解,比如:FeignClient注解。Feign有可插拔的注解,包括Feign注解和JAX-RS注解。Feign也支持编码器和解码器,Spring Cloud Open Feign对Feign进行增强支持Spring MVC注解,可以像Spring Web一样使用HttpMessageConverters等。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。RPC请求,对内RPC,对外Restful。
功能可插拔的注解支持,包括Feign注解和JAX-RS注解。支持可插拔的HTTP编码器和解码器(Gson,Jackson,Sax,JAXB,JAX-RS,SOAP)。支持Hystrix和它的Fallback。支持Ribbon的负载均衡,集成了Ribbon,默认轮询。支持HTTP请求和响应的压缩。灵活的配置:基于 name 粒度进行配置支持多种客户端:JDK URLConnection、apache httpclient、okhttp,ribbon)支持日志支持错误重试url支持占位符可以不依赖注册中心独立运行
Feign作用:
Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实规下,我们只需要创建一个借口并使用注解的方式来配置它(Dao接口的Mapper注解,类似,这里是Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign与OpenFeign的区别:
Feign量Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端 ,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。也是注解接口Feign,调用接口服务。
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解@RequestMapping。OpenFeign的@FeginClent可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均简并调用其他服务。
OpenFeign使用
面向接口
cloud-payment-service服务提供者controller:
/**
* 查询
* http://localhost:8001/payment/get/31
*
* @param id
* @return
*/
@GetMapping(value = "payment/get/{id}")
public ResponseResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果: " + payment);
if (payment != null) {
return new ResponseResult(200, "查询成功,serverPort:" + serverPort, payment);
}
return new ResponseResult(444, "没有对应记录,查询ID:" + id, null);
}
// 超时测试
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
// 暂停3秒钟
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
**服务提供者需要ribbon依赖/eureka客户端依赖,并且注入ribbon负载均衡算法Bean。**不然报错没有ribbon负载均衡。
新建对应服务提供者的feign模块,不用启动类和配置yml,不是微服务。
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建feign接口,该接口跟服务提供者的controller一样,除了名字,就是controller的接口,加上@FeignClient(value = "cloud-payment-service")
注解,value是服务提供者的服务名字。
/**
* @author wzq
* @create 2020-02-19 23:59
*/
@Component
@FeignClient(value = "cloud-payment-service") //对应服务的名字
//@RequestMapping(value = "/content")
public interface PaymentFeign {
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping(value = "payment/get/{id}")
ResponseResult getPaymentById(@PathVariable("id") Long id);
/**
* 模拟feign超时
*
* @return
*/
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeout();
}
另外微服务调用feign接口
在该微服务中添加openfeign依赖,启动类加上@EnableFeignClients(basePackages = {"feign接口的包名"})
启动feign调用,可以多个包。
@Resource
private PaymentFeign paymentFeign;
ResponseResult<List<Content>> categoryresut = paymentFeign.getPaymentById(Long.valueOf(id));
OpenFeign携带参数调用
服务消费方,feign调用其他服务,如果有token等权限参数,需要拦截携带过去。
too many bytes written问题
feign调用需要服务器提供者ribbon负载均衡,security拦截feign服务调用设置请求头需要去掉Content-Length,不然报错too many bytes written,以为是fegin请求参数大小问题,结果不是,服务之间调用需要携带一些用户信息之类的,所以实现了Feign的RequestInterceptor
拦截器复制请求头,复制的时候是所有头都复制的,可能导致Content-length长度跟body不一致. 所以只需要判断如果是Content-length就跳过。https://blog.csdn.net/qq_39986681/article/details/107138740
代码如下:
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* Title:feign添加token
* Description:调用其它服务的feign接口前,拦截请求,添加token
* 把当前用户的请求头封装给下一个微服务,feign调用之前拦截
* @author WZQ
* @version 1.0.0
* @date 2021/3/30
*/
@Configuration
public class FeignInterceptor implements RequestInterceptor {
// feign底层是RequestTemplate
@Override
public void apply(RequestTemplate requestTemplate) {
try {
//使用RequestContextHolder工具获取request相关变量
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
//取出request
HttpServletRequest request = attributes.getRequest();
//获取所有头文件信息的key
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
//头文件的key
String name = headerNames.nextElement();
// 跳过 content-length,不会feign调用出错 too many bytes written
if (name.equals("content-length")){
continue;
}
//头文件的value
String values = request.getHeader(name);
//将令牌数据添加到头文件中
requestTemplate.header(name, values);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
feign设置请求参数大小:
# feign请求携带参数数量,设置大点,避免too many bytes written (2ms)
feign:
compression:
request:
min-request-size: 4096
OpenFeign超时设置
服务提供者:
// 超时测试
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
// 暂停3秒钟
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
feign接口:
/**
* @author wzq
* @create 2020-02-19 23:59
*/
//@Component
@FeignClient(value = "cloud-payment-service") //对应服务的名字
public interface PaymentFeign {
/**
* 模拟feign超时
*
* @return
*/
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeout();
}
调用:
@Resource
private PaymentFeign paymentFeign;
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
// openfeign-ribbon, 客户端一般默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
由于服务提供者controller中设置停留3秒,其他微服务feign调用该接口就会超时请求,默认openfeign-ribbon客户端只等待1秒钟。
为了避免这样的超时情况,我们需要设置feign客户端(调用feign接口的微服务)中超时控制,yml添加:
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
这里超时设置成5秒,连接也是5秒。
OpenFeign日志处理
调用feign接口的微服务,如果需要知道feign调用的情况,可以通过打印日志查看。
openfeign提供了日志打印功能。
Logger有四种类型:NONE(默认)
、BASIC
、HEADERS
、FULL
,通过注册Bean来设置日志记录级别。NONE(默认)不需要注入。
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
在调用feign接口的微服务中,设置config:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* OpenFeignClient配置
*
* @author zzyy
* @create 2020/3/6 18:02
**/
@Configuration
public class FeignConfig {
/**
* feignClient配置日志级别
*
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
// 请求和响应的头信息,请求和响应的正文及元数据
return Logger.Level.FULL;
}
}
并在yml中配置:
logging:
level:
# feign日志以什么级别监控哪个接口
com.wzq.springcloud.feign.PaymentFeign: debug
调用,打印如下: