文章目录
OpenFeign基本介绍
概述
服务之间如果需要相互访问,可以使用RestTemplate, 也可以使用 Feign客户端访问。它默认会使用Ribbon来实现负载均衡。
可以理解为Feign是一个超级方便的调用Spring Cloud远程服务的框架/工具,帮助开发者以更少耦合,更少代码,更快更兼容的方法进行远程服务调用。
什么是服务调用
顾名思义,就是服务之间的接口互相调用,在微服务架构中很多功能都需要调用多个服务才能完成某一项功能。
为什么要使用Feign
Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。
Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。
Feign vs OpenFeign
Feign内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。
Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用Spring Cloud孵化了 OpenFeign。并且支持了Spring MVC的注解,如@RequestMapping,@PathVariable等等。
OpenFeign的 @FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。
Feign的实现流程
- 首先通过 @EnableFelgnClients注解开启FeignClient的功能 ,只有这个注解存在,才会在程序启动时开启对 @FeignClient注解的包扫描。
- 根据Feign的规则实现接口,并在接口上加上@FeignClient注解。
- 程序启动后,会进行包扫描,扫描所有的@FeignClient注解的类,并将这些信息注入到lOC容器中。
- 当接口中的方法被调用时,通过JDK的动态代理生成具体的 RequestTemplate模板对象 。
- 根据RequestTemplate再生成HTTP的Request对象。
- Request对象交给Client处理,其中Client网络请求框架可以是 HttpURLConnection, HttpClient与OkHttp(主要看依赖是什么)。
- 最后,Client被封装到LoadBalanceClient类,该类结合Ribbon做到了负载均衡。
OpenFeign使用步骤
1、服务消费端的POM文件添加依赖
<!-- spring cloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、yml配置文件
server:
port: 80
spring:
application:
name: cloud-consumer-service
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
3、启动类添加@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients // 开启对 @FeignClient注解的包扫描
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
4、新建RemoteUserService.java服务接口、使用@FeignClient注解
@Component
@FeignClient(value = "cloud-provider-service") // 服务提供者名称
public interface RemoteUserService {
@RequestMapping("/user/search/v1") // 要调用的接口
RestResult<List<UserTest>> searchUser(@RequestBody UserTest userTest);
}
5、消费者ConsumerController.java新增查询用户方法
@RestController
@Api(tags = "消费端")
public class ConsumerController {
@Autowired
private RemoteUserService remoteUserService;
@ApiOperation(value = "查询用户")
@PostMapping(value = "/search/v2")
public RestResult<List<UserTest>> searchUser(@RequestBody UserTest userTest) {
try {
RestResult<List<UserTest>> listRestResult = remoteUserService.searchUser(userTest);
return listRestResult;
} catch (Exception e) {
return RestResult.fail(ResultCode.DATA_ACCESS_ERROR);
}
}
}
6.测试
调用成功
OpenFeign超时控制
OpenFeign默认等待1秒钟,超过后报错
YML配置文件里设置OpenFeign客户端超时时间
feign:
client:
config:
#这里填具体的服务名称(也可以填default,表示对所有服务生效)
cloud-provider-service:
#connectTimeout和readTimeout这两个得一起配置才会生效
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
connectTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
readTimeout: 5000
cloud-provider-service2: # 每个服务单独配置
connectTimeout: 2000
readTimeout: 2000
OpenFeign日志增强
Feign提供了日志打印功能,我们可以通过配置来调整日恙级别,从而了解Feign 中 Http请求的细节。
日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
FeignConfig配置类
/**
* FeignConfig配置类
*/
@Configuration
public class FeignConfig {
/**
* 创建重试器 (重试周期(50毫秒),最大重试周期(2000毫秒),最多尝试次数 6次 )
* feign没有采用线性的重试机制而是采用的是一种指数级(乘法)的重试机制 每次重试时间 当前重试时间*= 1.5
* @FeignClient(value = "Eureka-client", configuration = FeignConfig.class) 进行配置
* @return
*/
@Bean
public Retryer getRetryer() {
return new Retryer.Default(50, TimeUnit.SECONDS.toMillis(2), 6);
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
YML文件里需要开启日志的Feign客户端
logging:
level:
# feign日志以什么级别监控哪个接口
com.da.springcloud.feignservice.RemoteUserService: debug
可以看到请求参数、返回结果等信息。
OpenFeign文件传输
OpenFeign本身不支持文件传输,需要在发送方进行一些配置。
添加feign支持提交from 表单的依赖
<!-- feign支持提交from 表单 -->
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.8.0</version>
</dependency>
feign接口
@FeignClient(name = ServiceNameConstants.OFFICE_2_PDF_SERVICE, fallbackFactory = RemoteOffice2PdfFallbackFactory.class, configuration = RemoteOffice2PdfService.FormSupportConfig.class)
public interface RemoteOffice2PdfService {
/**
* office2pdf
* @param file
* @param pdfEditPermissionPwd
* @return
*/
@PostMapping(value = "/office2pdf/o2p/conver/v1", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Response office2pdf(@RequestPart(value = "file") MultipartFile file, @RequestParam(value = "pdfEditPermissionPwd", required = false) String pdfEditPermissionPwd);
class FormSupportConfig {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
// new一个form编码器,实现支持form表单提交
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
}
服务端
/**
* office转pdf
* @param file office文件
* @param pdfEditPermissionPwd pdf编辑权限密码
* @return
*/
@ApiOperation(value = "office转pdf", notes = "office转pdf,仅支持word、ppt、excel")
@PostMapping(value = "/conver/v1", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "office文件,仅支持word、ppt、excel", required = true),
@ApiImplicitParam(name = "pdfEditPermissionPwd", value = "pdf编辑权限密码,为空则不设置权限")
})
public void conver(@RequestPart MultipartFile file, @RequestParam(value = "pdfEditPermissionPwd", required = false) String pdfEditPermissionPwd, HttpServletResponse response) {