GitHub OpenFeign是一种声明式调用
,我们只需要按照一定的规则描述我们的接口(不支持Spring MVC注解,它有一套自己的注解),它就能帮我们完成 REST风格的接口调用。
Spring Cloud将 GitHub OpenFeign封装成了 OpenFeign组件(spring-cloud-starter-openfeign),给出的规则完全支持 Spring MVC注解等。大大减少了代码的编写量,提高代码的可读性。
OpenFeign组件包含了对 Ribbon和 Hystrix的支持。
- 引入了Ribbon支持,是因为 OpenFeign的底层是通过 Ribbon来实现的
- 引入了Hystrix支持,是为了微服务之间的调用支持熔断功能。
注意:
只是对 Hystrix的支持,不包含 Hystrix本身,所以使用Hystrix还需要引入其依赖。
OpenFeign底层默认使用Ribbon,而 Ribbon默认使用的是 Apache HTTP Client作为底层连接。OpenFeign也可以对其修改(自行百度),比如 OK HTTP Client。
前面使用过 Ribbon+RestTemplate,接下来就开始走进 OpenFeign组件的使用。
二、OpenFeign组件的使用CRUDOpenFeign是一种声明式服务调用组件
,支持 SpringMVC注解
(@RequestMapping、@RequestBody 、@ResponseBody、@PathVariable、@RequestParam 等) ,它能让 REST 调用更加简单,可以快速上手,完成微服务间调用。
OpenFeign使用的真正难点在于如何传递参数?
OpenFeign参数传递特点:
- 参数一定要绑定参数名。
- 方法中的参数类型与参数的个数一定要和被调用的微服务方法中的参数类型和个数保持一致
- 接口编写时,方法一般与被调用的微服务方法一致。
- 接口编写时,方法一般与被调用的微服务方法一致,若被调用的微服务方法中使用了SpringMVC注解,在接口中编写的方法也一定要加上 SpringMVC注解
- 接口中编写的方法返回值一定要与被调用的微服务方法中返回值保持一致。
下面写一些常用的 CRUD传参场景,这里让 user服务调用 order服务。
搭建项目,传送门:Eureke服务治理中心
1、引入依赖在 user服务中引入 OpenFeign依赖。
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2、启动类上开启功能
在启动类上加 @EnableFeignClients 注解
,使用它的目的是驱动 OpenFeign工作并指定扫描包,对带有 @FeignClient注解
的接口进行扫描,并将它们装配到 Ioc容器中。
如果你的 Feign 接口定义跟你的启动类不在同一个包名下,还需要指定 basePackages
属性的值,即需要扫描的包名。
@SpringCloudApplication
@EnableFeignClients
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
3、编写服务提供者接口
order服务中基于 RESTful来编写提供的接口。
@RestController
public class OrderController {
@Value("${server.port}")
private String port;
@GetMapping("/get1/{id}")
@ResponseBody
public String get1(@PathVariable Long id) throws InterruptedException {
String data = port + "--" + "order服务get1返回数据 >>>>>>> " + id;
return data;
}
@GetMapping("/get2")
public ResponseEntity get2(@RequestParam("id") Long id, @RequestParam("orderName") String orderName) {
OrderDO orderDO = new OrderDO();
orderDO.setId(id);
orderDO.setOrderName(orderName);
orderDO.setCreateData(LocalDate.now());
return ResponseEntity.ok(orderDO);
}
@PostMapping("/post1")
public ResponseEntity post1(@RequestBody OrderDO orderDO) {
orderDO.setId(100L);
return ResponseEntity.ok(orderDO);
}
@PostMapping("/post2/{id}")
public ResponseEntity post2(@RequestBody OrderDO orderDO, @PathVariable("id") Long id, @RequestParam("orderName") String orderName) {
orderDO.setId(id);
orderDO.setOrderName(orderName);
return ResponseEntity.ok(orderDO);
}
@PutMapping("/put")
public void put(@RequestBody OrderDO orderDO) {
orderDO.setId(200L);
System.out.println(orderDO);
}
@DeleteMapping("/delete")
public void delete(@RequestHeader("id") Long id, @RequestHeader("orderName") String orderName) throws UnsupportedEncodingException {
//解码
orderName = URLDecoder.decode(orderName, "UTF-8");
System.out.println("删除成功==" + id + "-" + orderName);
}
@PostMapping("/uploadFile1")
public ResponseEntity uploadFile1(@RequestPart("file") MultipartFile multipartFile){
System.out.println("文件名:" + multipartFile.getOriginalFilename() + ", 文件大小:" + multipartFile.getSize());
return ResponseEntity.ok("上传ok");
}
@PostMapping("/uploadFile2")
public ResponseEntity uploadFile2(@RequestPart("files") MultipartFile[] multipartFiles){
System.out.println("文件数量:" + multipartFiles.length);
return ResponseEntity.ok("上传ok");
}
}
4、编写服务调用者接口
user服务通过 OpenFeign来调用order服务。
(1)编写 OpenFeign接口
@FeignClient注解
:表示这个接口是一个 OpenFeign的客户端,底层将使用 Ribbon执行 REST风格调用。value 指向调用的服务名
@FeignClient(value = "ORDER")
public interface OrderFeign {
@GetMapping("/order/get1/{id}")
String get1(@PathVariable("id") Long id);
@GetMapping("/order/get2")
ResponseEntity get2(@RequestParam("id") Long id, @RequestParam("orderName") String orderName);
@PostMapping("/order/post1")
ResponseEntity post1(@RequestBody OrderDO orderDO);
@PostMapping("/order/post2/{id}")
ResponseEntity post2(@RequestBody OrderDO orderDO, @PathVariable("id") Long id, @RequestParam("orderName") String orderName);
@PutMapping("/order/put")
void put(@RequestBody OrderDO orderDO);
@DeleteMapping("/order/delete")
void delete(@RequestHeader("id") Long id, @RequestHeader("orderName") String orderName);
@PostMapping(
value = "/order/uploadFile1",
// consumes指定提交的是一个 multipart/form-data类型的表单
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity uploadFile1(@RequestPart("file") MultipartFile multipartFile);
@PostMapping(
value = "/order/uploadFile2",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity uploadFile2(@RequestPart("files") MultipartFile[] multipartFiles);
}
(2)对外提供访问的API接口
@RestController
@RequestMapping("/userFeign")
public class UserFeignController {
@Autowired
private OrderFeign orderFeign;
@GetMapping("/get1/{id}")
public String get1(@PathVariable("id") Long id) {
String res = orderFeign.get1(id);
return res;
}
@GetMapping("/get2")
public ResponseEntity get2(@RequestParam("id") Long id, @RequestParam("orderName") String orderName) {
ResponseEntity responseEntity = orderFeign.get2(id, orderName);
return responseEntity;
}
@PostMapping("/post1")
public ResponseEntity post1(@RequestBody OrderDO orderDO) {
ResponseEntity responseEntity = orderFeign.post1(orderDO);
return responseEntity;
}
@PostMapping("/post2/{id}")
public ResponseEntity post2(@RequestBody OrderDO orderDO, @PathVariable("id") Long id, @RequestParam("orderName") String orderName){
ResponseEntity responseEntity = orderFeign.post2(orderDO, id, orderName);
return responseEntity;
}
@PutMapping("/put")
public void put(@RequestBody OrderDO orderDO){
orderFeign.put(orderDO);
}
@DeleteMapping("/delete")
public void delete(@RequestHeader("id") Long id, @RequestHeader("orderName") String orderName) throws UnsupportedEncodingException {
orderName = URLDecoder.decode(orderName, "UTF-8");
System.out.println("前端传递的中文:" + orderName);
// 通过header传参数,一定要中文转码之后再传递。否则无法访问
orderName = URLEncoder.encode(orderName, "UTF-8");
orderFeign.delete(id, orderName);
}
// 传参文件
@PostMapping("/uploadFile1")
public ResponseEntity uploadFile1(@RequestPart("file") MultipartFile multipartFile){
ResponseEntity responseEntity = orderFeign.uploadFile1(multipartFile);
return responseEntity;
}
@PostMapping("/uploadFile2")
public ResponseEntity uploadFile2(@RequestPart("files") MultipartFile[] multipartFiles){
ResponseEntity responseEntity = orderFeign.uploadFile2(multipartFiles);
return responseEntity;
}
}
5、测试
这里通过 postman测试,访问ok。主要说明一下:
- 通过header传参数,一定要中文转码。
- 传参是文件时。需要配置 consumes来声明类型。
- 根据请求方式的不同,灵活使用 SpringMVC注解
配置 OpenFeign的方法有很多种,最直观的是在 @FeignClient注解上配置。这里列举几个:
- value: 配置客户端的名称,一般为微服务名称
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- configuration: OpenFeign客户端的配置类,可以配置编码器Encoder、解码器Decoder、协议Contract,日志级别等。
- fallback: 指定 Hystrix降级服务类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑。
- fallbackFactory: 指定降级工厂类,用于生成fallback类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
OpenFeign默认在调用中并不是最详细日志输出,在调试程序时可以开启OpenFeign的详细日志展示。
OpenFeign中可以通过配置日志查看整个请求调用的过程。日志级别分四种:
- NONE:不开启日志(默认方案)
- BASIC:仅仅记录请求方法、URL、响应状态码、执行时间
- HEADER: 在Basic级别的基础上,增加记录请求和响应的header
- FULL:在 HEADER级别基础上,增加body及请求元数据
下面通过 yml配置文件设置即可。注意:
日志扫包级别必须设置为 debug。
feign:
client:
config:
#全局开启服务日志展示(所有的配置)
default:
loggerLevel: FULL
#日志扫包(feign客户端需在此包下),日志级别必须是debug级别
logging:
level:
# 这里扫包为com.example.user
com:
example:
user: debug
Openfeign 其底层是基于Ribbon的 ,并且使用 OpenFeign默认的是轮询的负载均衡策略。
这里就不修改策略了,启动两个order服务实例,验证一下轮询策略。启动类不用配置 @LoadBalanced负载均衡。
上面的demo中是没有服务降级功能。新版 Openfeign中的没有开启Hystrix(feign.hystrix.enabled=false)的使用。这里开启使用 Hystrix。
feign:
hystrix:
enabled: true
实现服务降级的主要是 @FeignClient配置项中的 fallback和 fallbackFactory。
注意:
这两者同时使用时,只会启用 fallback,不会启用 fallbackFactory。
(1) fallback配置项
- 定义一个定义服务降级的类 该必须实现 @FeignClient标记的接口。使用
@Component
将 该类装配到 Ioc容器中。在该类中重写实现接口的降级方法
@Component
public class OrderFeignFallback implements OrderFeign {
@Override
public String get1(Long id) {
return null;
}
@Override
public ResponseEntity get2(Long id, String orderName) {
return ResponseEntity.ok(new OrderDO());
}
@Override
public ResponseEntity post1(OrderDO orderDO) {
return ResponseEntity.ok(new OrderDO());
}
@Override
public ResponseEntity post2(OrderDO orderDO, Long id, String orderName) {
return ResponseEntity.ok(new OrderDO());
}
@Override
public void put(OrderDO orderDO) {
}
@Override
public void delete(Long id, String orderName) {
}
@Override
public ResponseEntity uploadFile1(MultipartFile multipartFile) {
return ResponseEntity.ok(null);
}
@Override
public ResponseEntity uploadFile2(MultipartFile[] multipartFiles) {
return ResponseEntity.ok(null);
}
}
- @FeignClient标记的接口中
fallback配置项
指向服务降级的类
重启项目,将 order服务关闭时,访问 order服务,可以看到服务熔断了,进去到了服务降级的方法中。 fallback配置项有个缺点:我们无法获取异常信息。需要获取异常信息可以使用 fallbackFactory配置项。
(2)fallbackFactory配置项
- 创建一个定义服务降级工厂的类 实现
FallbackFactory
接口,并指定T泛型为@FeignClient标记的接口。使用@Component
将该类装配到 Ioc容器中。
@Component
public class OrderFeignFallbackFactory implements FallbackFactory {
// 通过 create方法参数获取异常信息
@Override
public OrderFeign create(Throwable error) {
return new OrderFeign() {
@Override
public String get1(Long id) {
return error.getMessage();
}
@Override
public ResponseEntity get2(Long id, String orderName) {
return null;
}
@Override
public ResponseEntity post1(OrderDO orderDO) {
return null;
}
@Override
public ResponseEntity post2(OrderDO orderDO, Long id, String orderName) {
return null;
}
@Override
public void put(OrderDO orderDO) {
}
@Override
public void delete(Long id, String orderName) {
}
@Override
public ResponseEntity uploadFile1(MultipartFile multipartFile) {
return null;
}
@Override
public ResponseEntity uploadFile2(MultipartFile[] multipartFiles) {
return null;
}
};
}
- 通过 create方法参数获取异常信息,返回 T泛型类。
- @FeignClient标记的接口中
fallbackFactory
配置项指向服务降级工厂的类。
我这里使用匿名类,返回类型这里没有包装,就看一下 get1方法返回错误信息。
在启用 Hystrix的情况下,OpenFeign的调用分为两层,一个是 Ribbon,一个是 Hystrix。两者的默认超时时间都是 1s。
消费者端注意:
设置OpenFeign调用超时时间必须大于 Hystrix的超时时间。
hystrix:
command:
default:
execution:
isolation:
thread:
# 设置hystrix超时时间(单位毫秒),默认1000ms
timeoutInMilliseconds: 3000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 5000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 20 # 熔断触发最小请求次数,默认值是20
feign:
hystrix:
enabled: true
client:
config:
#全局开启服务日志展示(所有的配置)
default:
#连接服务器超时时间 单位为毫秒
connectTimeout: 2000
#调用超时时间 单位为毫秒
readTimeout: 6000
6、OpenFeign自定义请求拦截器
OpenFeign支持请求拦截器,在发送请求前,可以对发送的请求进行定制操作,可以定制RestTemplate 和一些请求参数、请求体等。比如:token信息。
使用步骤如下:
- 自定请求拦截器类实现 feign.
RequestInterceptor
接口,该接口的方法 apply 有参数类型 RequestTemplate,可以根据实际业务对请求信息进行调整。
public class UserInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// 在发送请求前增加了一个请求头信息,用于进行身份校验。一般传递token信息等
requestTemplate.header("token", "ssafj12312");
}
}
- 还需要在配置文件中启动这个拦截器。可以配置多个拦截器,但是OpenFeign并不保证拦截器的顺序。
feign:
hystrix:
enabled: true
client:
config:
#全局开启服务日志展示(所有的配置)
default:
# 配置拦截器
request-interceptors:
- com.example.user.interceptor.UserInterceptor
这里在 user服务中配置了请求拦截器,在order服务中简单获取下。
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。