SpringCloud学习笔记(四)——OpenFeign

一、OpenFegin简介

Feign 是声明性(注解)Web 服务客户端。它使编写 Web 服务客户端更加容易。要使用 Feign,请创建一个接口并对其进行注解。它具有可插入注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 还支持可插拔编码器和解码器。Spring Cloud 添加了对 Spring MVC 注解的支持,并支持使用 HttpMessageConverters,Spring Web 中默认使用的注解。Spring Cloud 集成了 Ribbon 和 Eureka 以及 Spring Cloud LoadBalancer,以在使用 Feign 时提供负载平衡的 http 客户端。
Feign 是一个远程调用的组件 (接口,注解) http 调用的
Feign 集成了 ribbon,ribbon 里面集成了 eureka

二、OpenFegin入门

2.1 本次调用设计图

 

2.2  首先启动一台Eureka Server

2.3 其次创建一个provider-order-service

 

然后修改pom文件,在主类中增加注解。

修改配置文件如下:

server:
  port: 8081
spring:
  application:
    name: provider-order-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true

新建一个controller,写一个访问接口。

@RestController
public class OrderController {

    @GetMapping("doOrder")
    public String doOrder() {
        System.out.println("有用户来下单了");
        return "下单成功!";
    }
}

然后启动用例,并进行测试。

 

 

2.4 其次创建一个consumer-user-service

 

 同样修改pom文件并在主类中增加配置。

配置文件增加内容如下:

server:
  port: 8082
spring:
  application:
    name: consumer-user-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true

新建一个fegin文件夹,并在其中增加一个fegin接口:

 

 接口中代码如下:

//@FeignClient 声明是 feign 的调用
// value = "provider-order-service" value 后面的值必须和提供者的服务名一致
@FeignClient(value = "provider-order-service")
public interface UserOrderFegin {

    /**
     * 描述: 下单的方法 这里的路径必须和提供者的路径一致
     *
     * @param :
     * @return java.lang.String
     */
    @GetMapping("doOrder")
    String doOrder();

}

新建一个controller,并在其中增加一个接口。

@RestController
public class UserController {

    @Autowired
    private UserOrderFegin userOrderFeign;
    /**
     * 用户远程调用下单的接口
     *
     * @return
     */
    @GetMapping("userDoOrder")
    public String userDoOrder() {
        String result = userOrderFeign.doOrder();
        System.out.println(result);
        return result;
    }

}

记得还要在主类中增加一个注解,否则那个fegin接口自动注入时会报错。

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //标记 feign 的客户端
public class ConsumerUserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerUserServiceApplication.class, args);
    }

}

然后我们启动这个项目并进行测试。

 

 

 

 说明已经成功访问到订单服务器了。

2.5 本次调用总结

2.6 防止order-service那一段访问处理逻辑时间过长的解决办法

因 为 ribbon 默 认 调 用 超 时 时 长 为 1s , 可 以 修 改 , 超 时 调 整 可 以 查 看DefaultClientConfigImpl。
然后在user-service这一端配置上相应的参数:
ribbon: #feign 默认调用 1s 超时
ReadTimeout: 5000 #修改调用时长为 5s
ConnectTimeout: 5000 #修改连接时长为 5s

三、自己手写一个fegin调用

四、fegin远程调用的传参问题

 在之前provider-order-service和consumer-user-service的基础上进行传参调用。

4.1 参数传递规范

Feign 传参确保消费者和提供者的参数列表一致 包括返回值 方法签名要一致
1. 通过 URL 传参数,GET 请求,参数列表使用@PathVariable(“”)
2. 如果是 GET 请求,每个基本参数必须加@RequestParam(“”)
3. 如果是 POST 请求,而且是对象集合等参数,必须加@Requestbody 或者@RequestParam

4.2 修改provider-order-service中的内容

首先创建一个bean类order,用来测试post方法传递对象,代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Order {
    private Integer id;
    private String name;
    private Double price;
    private Date time;
}

然后创建一个ParaController类,里边提供一下接口供user-service调用,代码如下:

@RestController
public class ParaController {
    /*
     *测试的参数有五类:
     * 1.只有一个参数的get请求;
     * 2.有多个参数的get请求;
     * 3.只有一个对象参数的post请求;
     * 4.有一个对象参数和一个普通参数的post请求;
     * 5.url请求
     */

    /*
     *测试单个参数get
     *注意:如果注解中没有required=false则这个参数是必须要传过来的
     */
    @GetMapping("testOnePara")
    public String testOnePara(@RequestParam(value="name",required = false) String name) {
        System.out.println("name:"+name);
        return "success";
    }

    /*
     *测试两个参数的get请求,两个参数最好都加上注解
     */
    @GetMapping("testTwoPara")
    public String testTwoPara(@RequestParam String name,@RequestParam Integer age) {
        System.out.println("name:"+name+",age:"+age);
        return "success";
    }

    /*
     *测试一个对象的post请求,注意要用的注解
     */
    @PostMapping("testObjectPara")
    public String testObjectPara(@RequestBody Order order) {
        System.out.println(order);
        return "success";
    }

    /*
     *测试一个对象和一个参数的请求
     */
    @PostMapping("testObjectAndPara")
    public String testObjectAndPara(@RequestBody Order order,@RequestParam String name) {
        System.out.println("order:"+order);
        System.out.println("name:"+name);
        return "success";
    }

    @GetMapping("testUrlParam/{id}/and/{name}")
    public String testUrlParam(@PathVariable("id") Integer id,@PathVariable("name") String name) {
        System.out.println("id:"+id);
        System.out.println("name:"+name);
        return "success";
    }

}

4.3 修改consumer-user-service

首先同样创建bean类oder;

然后修改fegin接口,增加远程调用接口的抽象方法,整体代码如下:

@FeignClient(value = "provider-order-service")
public interface UserOrderFegin {

    /**
     * 描述: 下单的方法 这里的路径必须和提供者的路径一致
     *
     * @param :
     * @return java.lang.String
     */
    @GetMapping("doOrder")
    String doOrder();

    /*
     *测试单个参数get
     *注意:如果注解中没有required=false则这个参数是必须要传过来的
     */
    @GetMapping("testOnePara")
    public String testOnePara(@RequestParam(value="name",required = false) String name) ;

    /*
     *测试两个参数的get请求,两个参数最好都加上注解
     */
    @GetMapping("testTwoPara")
    public String testTwoPara(@RequestParam String name,@RequestParam Integer age);

    /*
     *测试一个对象的post请求,注意要用的注解
     */
    @PostMapping("testObjectPara")
    public String testObjectPara(@RequestBody Order order);

    /*
     *测试一个对象和一个参数的请求
     */
    @PostMapping("testObjectAndPara")
    public String testObjectAndPara(@RequestBody Order order,@RequestParam String name);

    @GetMapping("testUrlParam/{id}/and/{name}")
    public String testUrlParam(@PathVariable("id") Integer id, @PathVariable("name") String name);

}

新建一个controller类或者在其它controller类中增加一个接口方法。

@RestController
public class ParaController {

    @Autowired
    private UserOrderFegin userOrderFegin;

    @GetMapping("testPara")
    public String testPara() {
        String test1 = userOrderFegin.testOnePara("zhangsan1");
        System.out.println("测试单参数:"+test1);
        String test2 = userOrderFegin.testTwoPara("lisi",25);
        System.out.println("测试双参数:"+test2);
        Order order = Order.builder().id(111).name("蔬菜").price(15.5).time(new Date()).build();
        String test3 = userOrderFegin.testObjectPara(order);
        System.out.println("测试一个对象:"+test3);
        String test4 = userOrderFegin.testObjectAndPara(order,"wangwu");
        System.out.println("测试一个对象和一个参数:"+test4);
        String test5 = userOrderFegin.testUrlParam(15,"zhaoliu");
        System.out.println("测试url传参:"+test5);
        return "success";
    }

4.4 测试

@RestController
public class ParaController {

    @Autowired
    private UserOrderFegin userOrderFegin;

    @GetMapping("testPara")
    public String testPara() {
        String test1 = userOrderFegin.testOnePara("zhangsan1");
        System.out.println("测试单参数:"+test1);
        String test2 = userOrderFegin.testTwoPara("lisi",25);
        System.out.println("测试双参数:"+test2);
        Order order = Order.builder().id(111).name("蔬菜").price(15.5).time(new Date()).build();
        String test3 = userOrderFegin.testObjectPara(order);
        System.out.println("测试一个对象:"+test3);
        String test4 = userOrderFegin.testObjectAndPara(order,"wangwu");
        System.out.println("测试一个对象和一个参数:"+test4);
        String test5 = userOrderFegin.testUrlParam(15,"zhaoliu");
        System.out.println("测试url传参:"+test5);
        return "success";
    }

}

五、时间日期参数问题

如果时间类型被包装在对象里传递是没有问题的,但是如果将时间作为单个参数传递,那么就会差14个小时,这是时区造成的。

正确传递时间参数有下列方案:

1.转换成字符串传递(推荐使用)

2.使用 JDK8 的 LocalDate(日期) 或 LocalDateTime(日期和时间,接收方只有秒,没有毫秒)

 

 3.将时间参数找个对象包起来。

六、源码分析

根据上面的案例,我们知道 feign 是接口调用,接口如果想做事,必须要有实现类
可是我们并没有写实现类,只是加了一个@FeignClient(value=”xxx-service”)的注解
所以我们猜测 feign 帮我们创建了代理对象,然后完成真实的调用。
动态代理 1jdk (
invoke) 2cglib 子类继承的
1. 给接口创建代理对象(启动扫描)
2. 代理对象执行进入 invoke 方法
3. 在 invoke 方法里面做远程调用
具体我们这次的流程:
A. 扫描注解得到要调用的服务名称和 url

B. 拿到 provider-order-service/doOrder,通过 ribbon 的负载均衡拿到一个服务,
provider-order-service/doOrder---》http://ip:port/doOrder
C. 发起请求,远程调用

七、openFegin总结

OpenFeign 主要基于接口和注解实现了远程调用
源码总结:面试
1. OpenFeign 用过吗?它是如何运作的?在主启动类上加上@EnableFeignClients 注解后,启动会进行包扫描,把所有加了
@FeignClient(value=”xxx-service”)注解的接口进行创建代理对象通过代理对象,使用
ribbon 做了负载均衡和远程调用
2. 如何创建的代理对象?
当 项 目 在 启 动 时 , 先 扫 描 , 然 后 拿 到 标 记 了 @FeignClient 注 解 的 接 口 信 息 , 由
ReflectiveFeign 类的 newInstance 方法创建了代理对象 JDK 代理
3. OpenFeign 到底是用什么做的远程调用?
使用的是 HttpURLConnection (
java.net)
4. OpenFeign 怎么和 ribbon 整合的?
在代理对象执行调用的时候

八、OpenFegin的日志功能

从前面的测试中我们可以看出,没有任何关于远程调用的日志输出,如请头,参数。Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而揭开 Feign 中 Http 请求的所有细节。

8.1 查看OpenFegin的日志级别

NONE 默认的,不显示日志
BASE 仅记录请求方法,URL ,响应状态码及执行时间
HEADERS 在 BASE 之上增加了请求和响应头的信息
FULL 在 HEADERS 之上增加了请求和响应的正文及无数据

8.2 创建配置类或者直接在主类中添加如下方法

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLogger() {
        return Logger.Level.FULL;    
    }
}

8.3 然后修改配置文件,允许打印该类的日志信息

知识点:日志级别有哪些

logging:
  level:
    com.bjpowernode.feign.UserOrderFeign: debug

8.4 然后再次使用OpenFegin调用时看日志信息即可

 

posted @ 2022-10-19 18:07  一直学习的程序小白  阅读(545)  评论(0编辑  收藏  举报