OpenFeign组件声明式服务调用

一、OpenFeign简介

1、什么是OpenFeign

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组件的使用CRUD

OpenFeign是一种声明式服务调用组件,支持 SpringMVC注解(@RequestMapping、@RequestBody 、@ResponseBody、@PathVariable、@RequestParam 等) ,它能让 REST 调用更加简单,可以快速上手,完成微服务间调用。

OpenFeign使用的真正难点在于如何传递参数?OpenFeign参数传递特点:

  • 参数一定要绑定参数名。
  • 方法中的参数类型与参数的个数一定要和被调用的微服务方法中的参数类型和个数保持一致
  • 接口编写时,方法一般与被调用的微服务方法一致。
  • 接口编写时,方法一般与被调用的微服务方法一致,若被调用的微服务方法中使用了SpringMVC注解,在接口中编写的方法也一定要加上 SpringMVC注解
  • 接口中编写的方法返回值一定要与被调用的微服务方法中返回值保持一致。

下面写一些常用的 CRUD传参场景,这里让 user服务调用 order服务。

搭建项目,传送门:Eureke服务治理中心

1、引入依赖

在 user服务中引入 OpenFeign依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
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<OrderDO> 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<OrderDO> post1(@RequestBody OrderDO orderDO) {
        orderDO.setId(100L);
        return ResponseEntity.ok(orderDO);
    }

    @PostMapping("/post2/{id}")
    public ResponseEntity<OrderDO> 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<String> uploadFile1(@RequestPart("file") MultipartFile multipartFile){
        System.out.println("文件名:" + multipartFile.getOriginalFilename() + ", 文件大小:" + multipartFile.getSize());
        return ResponseEntity.ok("上传ok");
    }

    @PostMapping("/uploadFile2")
    public ResponseEntity<String> 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<OrderDO> get2(@RequestParam("id") Long id, @RequestParam("orderName") String orderName);

    @PostMapping("/order/post1")
    ResponseEntity<OrderDO> post1(@RequestBody OrderDO orderDO);

    @PostMapping("/order/post2/{id}")
    ResponseEntity<OrderDO> 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<String> uploadFile1(@RequestPart("file") MultipartFile multipartFile);

    @PostMapping(
            value = "/order/uploadFile2",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResponseEntity<String> 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<OrderDO> get2(@RequestParam("id") Long id, @RequestParam("orderName") String orderName) {
        ResponseEntity<OrderDO> responseEntity = orderFeign.get2(id, orderName);
        return responseEntity;
    }

    @PostMapping("/post1")
    public ResponseEntity<OrderDO> post1(@RequestBody OrderDO orderDO) {
        ResponseEntity<OrderDO> responseEntity = orderFeign.post1(orderDO);
        return responseEntity;
    }

    @PostMapping("/post2/{id}")
    public ResponseEntity<OrderDO> post2(@RequestBody OrderDO orderDO, @PathVariable("id") Long id, @RequestParam("orderName") String orderName){
        ResponseEntity<OrderDO> 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<String> uploadFile1(@RequestPart("file") MultipartFile multipartFile){
        ResponseEntity<String> responseEntity = orderFeign.uploadFile1(multipartFile);
        return responseEntity;
    }

    @PostMapping("/uploadFile2")
    public ResponseEntity<String> uploadFile2(@RequestPart("files") MultipartFile[] multipartFiles){
        ResponseEntity<String> responseEntity = orderFeign.uploadFile2(multipartFiles);
        return responseEntity;
    }

}
5、测试

这里通过 postman测试,访问ok。主要说明一下:

  • 通过header传参数,一定要中文转码。
  • 传参是文件时。需要配置 consumes来声明类型。
  • 根据请求方式的不同,灵活使用 SpringMVC注解
    在这里插入图片描述
    在这里插入图片描述

三、Openfeign自定义配置

1、了解 @FeignClient配置项的信息

配置 OpenFeign的方法有很多种,最直观的是在 @FeignClient注解上配置。这里列举几个:

  • value: 配置客户端的名称,一般为微服务名称
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址
  • configuration:
    OpenFeign客户端的配置类,可以配置编码器Encoder、解码器Decoder、协议Contract,日志级别等。
  • fallback: 指定 Hystrix降级服务类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑。
  • fallbackFactory: 指定降级工厂类,用于生成fallback类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
2、OpenFeign日志级别设置

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

在这里插入图片描述

3、OpenFeign负载均衡策略设置

Openfeign 其底层是基于Ribbon的 ,并且使用 OpenFeign默认的是轮询的负载均衡策略。

这里就不修改策略了,启动两个order服务实例,验证一下轮询策略。启动类不用配置 @LoadBalanced负载均衡。
在这里插入图片描述
在这里插入图片描述

4、配置 Hystrix,服务熔断降级

上面的demo中是没有服务降级功能。新版 Openfeign中的没有开启Hystrix(feign.hystrix.enabled=false)的使用。这里开启使用 Hystrix。

feign:
  hystrix:
    enabled: true

实现服务降级的主要是 @FeignClient配置项中的 fallback和 fallbackFactory。

注意: 这两者同时使用时,只会启用 fallback,不会启用 fallbackFactory。

(1) fallback配置项

  1. 定义一个定义服务降级的类
    该必须实现 @FeignClient标记的接口。使用@Component将 该类装配到 Ioc容器中。在该类中重写实现接口的降级方法
@Component
public class OrderFeignFallback implements OrderFeign {
    @Override
    public String get1(Long id) {
        return null;
    }

    @Override
    public ResponseEntity<OrderDO> get2(Long id, String orderName) {
        return ResponseEntity.ok(new OrderDO());
    }

    @Override
    public ResponseEntity<OrderDO> post1(OrderDO orderDO) {
        return ResponseEntity.ok(new OrderDO());
    }

    @Override
    public ResponseEntity<OrderDO> 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<String> uploadFile1(MultipartFile multipartFile) {
        return ResponseEntity.ok(null);
    }

    @Override
    public ResponseEntity<String> uploadFile2(MultipartFile[] multipartFiles) {
        return ResponseEntity.ok(null);
    }
}
  1. @FeignClient标记的接口中 fallback配置项指向服务降级的类
    在这里插入图片描述

重启项目,将 order服务关闭时,访问 order服务,可以看到服务熔断了,进去到了服务降级的方法中。
在这里插入图片描述
fallback配置项有个缺点:我们无法获取异常信息。需要获取异常信息可以使用 fallbackFactory配置项。

(2)fallbackFactory配置项

  1. 创建一个定义服务降级工厂的类
    实现 FallbackFactory<T>接口,并指定T泛型为@FeignClient标记的接口。使用@Component将该类装配到 Ioc容器中。
@Component
public class OrderFeignFallbackFactory implements FallbackFactory<OrderFeign> {
    // 通过 create方法参数获取异常信息
    @Override
    public OrderFeign create(Throwable error) {

        return new OrderFeign() {
            @Override
            public String get1(Long id) {
                return error.getMessage();
            }

            @Override
            public ResponseEntity<OrderDO> get2(Long id, String orderName) {
                return null;
            }

            @Override
            public ResponseEntity<OrderDO> post1(OrderDO orderDO) {
                return null;
            }

            @Override
            public ResponseEntity<OrderDO> 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<String> uploadFile1(MultipartFile multipartFile) {
                return null;
            }

            @Override
            public ResponseEntity<String> uploadFile2(MultipartFile[] multipartFiles) {
                return null;
            }
        };
    }

  1. 通过 create方法参数获取异常信息,返回 T泛型类。
  2. @FeignClient标记的接口中fallbackFactory配置项指向服务降级工厂的类。
    在这里插入图片描述

我这里使用匿名类,返回类型这里没有包装,就看一下 get1方法返回错误信息。
在这里插入图片描述

5、Openfeign超时时间

在启用 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. 求知若饥,虚心若愚。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值