首页 > 其他分享 >springcloud动力节点-03OpenFeign

springcloud动力节点-03OpenFeign

时间:2023-12-28 20:25:08浏览次数:31  
标签:调用 String service springcloud 节点 03OpenFeign import order public

Spring Cloud OpenFeign 

1.说在前面

上 一 节 我 们 讲 到 Ribbon 做 了 负 载 均 衡 , 用 Eureka-Client 来 做 服 务 发 现 , 通 过RestTemplate 来完成服务调用,但是这都不是我们的终极方案,终极方案是使用 OpenFeign

2.OpenFeign 简介

https://docs.spring.io/spring-cloud-openfeign/docs/2.2.4.RELEASE/reference/html/#spring-cloud-feign 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

3.OpenFeign 快速入门

3.1 本次调用的设计图

 

 

3.2 启动一个 eureka-server 服务,这里不重复演示,参考 eureka文档

3.3 先创建 provider-order-service,选择依赖 

 

3.4 provider-order-service 修改配置文件 

# 应用服务 WEB 访问端口
server:
  port: 8080
spring:
  application: # 注册名称
    name: order-service
eureka:
  client:
    service-url: # 注册地址
      defaultZone: http://localhost:8761/eureka
  instance:
  instance-id: ${spring.application.name}:${server.port}
  prefer-ip-address: true

3.5 provider-order-service 修改启动类增加一个访问接口

package com.tongda.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class OrderController {

    @GetMapping("doOrder")
    public String doOrder() {
        System.out.println("有用户来下单了");
        return "我是订单服务";
    }
}

3.6 provider-order-service 启动测试访问

 

 

3.7 再创建 consumer-user-service,选择依赖

 

3.8 consumer-user-service 修改配置文件

# 应用服务 WEB 访问端口
server:
  port: 8081
spring:
  application: # 注册名称
    name: user-service
eureka:
  client:
    service-url: # 注册地址
      defaultZone: http://localhost:8761/eureka
  instance:
  instance-id: ${spring.application.name}:${server.port}
  prefer-ip-address: true

3.9 consumer-user-service 创建一个接口(重点)

package com.tongda.feign;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 *
 * @FeignClient(value = "order-service")
 * value 就是提供者的应用名称,value 后面的值必须和提供者的服务名一致
 *
 */
@FeignClient(value = "order-service") // 注解(value=“应用者注册名称”)
public interface UserOrderFeign {

    // 直接调用编写controller中doorder方法
    /*
     * 你需要调用哪个controller,就写它的方法签名。
     * 方法签名(就是包含一个方法的所有的属性)
     * 描述: 下单的方法 这里的路径必须和提供者的路径一致
     * @param
     * @return {@link java.lang.String}
     *
     **/
    @GetMapping("doOrder")
    public String doOrder(); // 方法的所有的属性 就是 方法签名

} 

3.10 consumer-user-service 创建 controller 

package com.tongda.controller;

import com.tongda.feign.UserOrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    /*
     * 接口是不能做事情的
     * 如果想做事,必须要有对象
     * 那么这个接口肯定是被创建出代理对象的
     * 动态代理jdk(java interface 接口 $Proxy) cglib(subClass 子类)
     * jdk动态代理,只要是代理对象调用的方法必须走invoke方法
     * @param null
     * @return {@link null}
     *
     **/
    @Autowired
    private UserOrderFeign userOrderFeign;

    /*
     * 总结:
     * 浏览器(前端)---->user-service(/userDoOrder)---->RPC远程过程调用(feign)---->order-service(/doOrder接口)
     * feign的默认等待时间是 1s,超多1s就会报错:Time out超时
     * 可以用yml来配置超时设定
     * @param
     * @return {@link java.lang.String}
     *
     **/
    @GetMapping("userDoOrder")
    public String UserDoOrder() {
        System.out.println("有用户进来了");
        // 需要调用订单orderService,发起远程调用
        // 1.需要开启feign的客户端功能,修改启动类才可以帮助我们发起调用
        // 2.需要创建一个接口,包名feign,并加注解@FeignClient(value = "order-service") // 注解(value=“应用者注册名称”)
        String s = userOrderFeign.doOrder();
        return s;
    }
}

3.11 consumer-user-service 修改启动类 

package com.tongda;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient // eureka 开启
@EnableFeignClients // 开启feign的客户端功能,才可以帮助我们发起调用
public class UserServiceApplication {

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

3.12 启动调用测试 

 

访问:http://localhost:8081/userDoOrder 

 

3.13 本次调用总结

consumer-user-service---》 /userDoOrder ---》通过 feign 调用 /doOrder ---》provider-order-service 下单成功 

3.14 测试 feign 调用的负载均衡 

启动多台 provider-order-service: 

 

测试访问:

 

3.15 调用超时设置

因 为 ribbon 默 认 调 用 超 时 时 长 为 1s , 可 以 修 改 , 超 时 调 整 可 以 查 看DefaultClientConfigImpl
# feign只是帮你防撞了远程调用的功能,底层还是ribbon所以我们需要去修改ribbon的时间配置
ribbon:
  ReadTimeout: 3000 # 给3s查时间
  connectTimeout: 3000 # 链接服务的超时时间

4.OpenFeign 调用参数处理(开发重点)

4.1 说在前面

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

4.2 修改 provider-order-service

4.2.1 创建 BaseResult 类 :获取code\data、msg属性

public class BaseResult implements Serializable {
    private Integer code;
    private String msg;
    private Object data;
    public static BaseResult success(Integer code, String msg, Object data) {
        BaseResult baseResult = new BaseResult();
        baseResult.setCode(code);
        baseResult.setData(data);
        baseResult.setMsg(msg);
        return baseResult;
    }
}        

4.2.2 创建 Order 类

package com.tongda.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

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

4.2.3 创建 TestParamController 类

package com.tongda.controller;

import com.tongda.domain.Order;
import org.springframework.web.bind.annotation.*;

/**
 *
 * url   /doOrder/热干面/add/油条/aaa ;无需key=value
 * get传递一个参数
 * get传递多个参数
 * post传递一个对象
 * post传递一个对象+一个基本参数
 * post传递2个对象?
 * @Date 2023/7/17 7:44
 * @Version 1.0
 */
@RestController
public class ParamController {

    @GetMapping("testUrl/{name}/and/{age}")
    public String testUrl(@PathVariable("name")String name,@PathVariable("age")Integer age){
        System.out.println(name+":"+age);
        return BaseResult.success(200, "ok", order);
    }

    @GetMapping("oneParam")
    // get传递一个参数,省略传参:required = false
    public String oneParam(@RequestParam(required = false) String name){
        System.out.println(name);
        return BaseResult.success(200, "ok", order);
    }

    @GetMapping("twoParam")
    // get传递多个参数
    public String twoParam(@RequestParam(required = false)String name,@RequestParam(required = false)Integer age) {
        System.out.println(name);
        System.out.println(age);
        return BaseResult.success(200, "ok", order);
    }

    @PostMapping("oneObj")
    public String oneObj(@RequestBody Order order){
        System.out.println(order);
        return BaseResult.success(200, "ok", order);
    }

    @PostMapping("oneObjOneParam")
    // post传递一个对象+一个基本参数,body只能放一个对象,请求param参数?name
    public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name) {
        System.out.println(name);
        System.out.println(order);
        return "ok";
    }
}

4.3 修改 consumer-user-service

4.3.1 将 Order 类和 BaseResult 类拷贝过来,后面会抽到公共模块里

4.3.2 修改 UserOrderFeign 接口

package com.tongda.feign;

import com.tongda.domain.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 *
 * @FeignClient(value = "order-service")
 * value 就是提供者的应用名称,value 后面的值必须和提供者的服务名一致
 *
 */
@FeignClient(value = "order-service") // 注解(value=“应用者注册名称”)
public interface UserOrderFeign {

    // 直接调用编写controller中doorder方法
    /*
     * 你需要调用哪个controller,就写它的方法签名。
     * 方法签名(就是包含一个方法的所有的属性)
     * 描述: 下单的方法 这里的路径必须和提供者的路径一致
     * @param
     * @return {@link java.lang.String}
     *
     **/
    @GetMapping("doOrder")
    public String doOrder(); // 方法的所有的属性 就是 方法签名

    // 内部传参:技巧拷贝order下ParamController,去掉请求体
    @GetMapping("testUrl/{name}/and/{age}")
    public String testUrl(@PathVariable("name")String name, @PathVariable("age")Integer age);

    @GetMapping("oneParam")
    // get传递一个参数,省略传参:required = false
    public String oneParam(@RequestParam(required = false) String name);

    @GetMapping("twoParam")
    // get传递多个参数
    public String twoParam(@RequestParam(required = false)String name,@RequestParam(required = false)Integer age);

    @PostMapping("oneObj")
    public String oneObj(@RequestBody Order order);

    @PostMapping("oneObjOneParam")
    // post传递一个对象+一个基本参数,body只能放一个对象,请求param参数?name
    public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name);

}

4.3.3 创建 TestController 类

package com.tongda.controller;

import com.tongda.domain.Order;
import com.tongda.feign.UserOrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@RestController
public class UserController {

    /*
     * 接口是不能做事情的
     * 如果想做事,必须要有对象
     * 那么这个接口肯定是被创建出代理对象的
     * 动态代理jdk(java interface 接口 $Proxy) cglib(subClass 子类)
     * jdk动态代理,只要是代理对象调用的方法必须走invoke方法
     * @param null
     * @return {@link null}
     *
     **/
    @Autowired
    private UserOrderFeign userOrderFeign;

    /*
     * 总结:
     * 浏览器(前端)---->user-service(/userDoOrder)---->RPC远程过程调用(feign)---->order-service(/doOrder接口)
     * feign的默认等待时间是 1s,超多1s就会报错:Time out超时
     * 可以用yml来配置超时设定
     * @param
     * @return {@link java.lang.String}
     *
     **/
    @GetMapping("userDoOrder")
    public String UserDoOrder() {
        System.out.println("有用户进来了");
        // 需要调用订单orderService,发起远程调用
        // 1.需要开启feign的客户端功能,修改启动类才可以帮助我们发起调用
        // 2.需要创建一个接口,包名feign,并加注解@FeignClient(value = "order-service") // 注解(value=“应用者注册名称”)
        String s = userOrderFeign.doOrder();
        return s;
    }


    @GetMapping("testParam")
    public String testParam() {
        // url传参
        String juelan = userOrderFeign.testUrl("juelan", 18);
        System.out.println(juelan);

        // 一个参数
        String t = userOrderFeign.oneParam("老唐");
        System.out.println(t);

        // 两个参数
        String l = userOrderFeign.twoParam("老李", 32);
        System.out.println(l);

        // 对象:使用链式创建
        Order order = Order.builder()
                .name("牛排")
                .price(188D)
                .time(new Date())
                .id(1)
                .build();

        String s = userOrderFeign.oneObj(order);
        System.out.println(s);

        // 一个对象一个参数
        String h = userOrderFeign.oneObjOneParam(order, "汉");
        System.out.println(h);

        return "OK";
    }
}

4.3.4 测试调用

访问: http://localhost:8081/testFeignParam 

4.3.5 时间日期参数问题

使用 feign 远程调用时,传递 Date 类型,接收方的时间会相差 14 个小时,是因为时区造成 的 处理方案: 1. 使用字符串传递参数,接收方转换成时间类型(推荐使用)不要单独传递时间 2. 使用 JDK8 的 LocalDate(日期) 或 LocalDateTime(日期和时间,接收方只有秒,没有毫 秒) 3. 自定义转换方法 第一步:order-Service中ParamController
// 时间
    @GetMapping("testTime")
    public String testTime(@RequestParam Date date) {
        System.out.println(date);
        return "OK";
    }

第二步:user-Service中UserOrderFergn接口interface

// 时间
    @GetMapping("testTime")
    public String testTime(@RequestParam Date date);

第三步:user-Service中UserController

// 时间测试: 发现调用时间之间相差+—10小时
    /*
     * Sun Mar 20 10:24:23 CST 2022
     * Mon Mar 21 00:24:13 CST 2022 +- 14小时
     * 1. 不建议单独传递时间参数,出现上面问题
     * 2. 转成字符串解决问题: 2022-03-20 10:25:55:213 因为字符串不会改变
     * 3. jdk LocalDate 年月日 LocalDateTime 会丢失s秒
     * 4. 改feign的源码
     * 
     * @param
     * @return {@link java.lang.String}
     *
     **/
    @GetMapping("time")
    public String time() {
        Date date = new Date();
        System.out.println(date);
        String s = userOrderFeign.testTime(date);
        // 字符串转
        LocalDate now = LocalDate.now();// 年月日
        LocalDateTime now1 = LocalDateTime.now(); // 年月日时分秒
        return s;
    }
传参总结: get 请求只用来传递基本参数 而且加注解@RequestParam post 请求用来传递对象参数 并且加注解@RequestBody

5.OpenFeign 源码分析

 

(学习别人的思想,可以找 bug,优化你的代码,提高代码的 健壮性) 看源码之前要先大致猜想一下 他是怎么实现的?(先使用在分析)

5.1 OpenFeign 的原理是什么?

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

 

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

5.2 看看 OpenFeign 的内部是如何实现这些的

 

  

5.2.1 如何扫描注解@FeignClient

查看启动类的@EnableFeignClients

 

进入 FeignClientsRegistrar 这个类 去查看里面的东西

 

 

真正的扫描拿到注解和服务名称

 

5.2.2 如何创建代理对象去执行调用?

当我们启动时,在 ReflectiveFeign 类的 newInstance 方法,给接口创建了代理对象

 

当我们执行调用的时候,打个断点去查看

 

ReflectiveFeign 类中的 invoke 方法帮我们完成调用

 

SynchronousMethodHandler 的 invoke 中给每一个请求创建了一个 requestTemplate 对象,去执行请求

 

executeAndDecode

 

我们去看 LoadBalancerFeignClient 的 execute 方法

 

executeWithLoadBalancer 继续往下看

 

 

只要是 feign 调用出了问题 看 feign 包下面的 Client 接口下面的 108 行 200 成功 400 请求参数错误 429 被限流 401 没有权限;无token 403 权限不够;token 404 路径不匹配 405 方法不允许 500 提供者报错了 302 资源重定向

 6.OpenFeign 总结

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

 7.OpenFeign 其他

7.1 OpenFeign 的日志功能

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

7.1.1 OpenFeign 的日志级别

 

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

7.1.2 创建配置类

package com.tongda;

import feign.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient // eureka 开启
@EnableFeignClients // 开启feign的客户端功能,才可以帮助我们发起调用
public class UserServiceApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // 配置日志:FULL 打印全部信息级别feign,还需要yml中配置开启
    @Bean
    public Logger.Level level() {
        return Logger.Level.FULL;
    }

}

7.1.3 修改配置文件

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

7.1.4 调用测试

 

 

标签:调用,String,service,springcloud,节点,03OpenFeign,import,order,public
From: https://www.cnblogs.com/yayuya/p/17538421.html

相关文章

  • Spring Cloud动力节点-07Alibaba简介、注册、配置中心
    1.项目简介SpringCloudAlibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过SpringCloud编程模型轻松使用这些组件来开发分布式应用服务。依托SpringCloudAlibaba,您只需要添加一些注解和少量配置,就可以将SpringClo......
  • springcloud动力节点-06Admin监控 Or Gateway网关
    SpringCloudAdmin 监控端点新建工程:admin-serverpom中springcloud版本号和版本控制要添加<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan......
  • 微服务框架 SpringCloud微服务架构3
    微服务框架SpringCloud微服务架构3Eureka3.1提供者与消费者3.1.1一些概念服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)在我们的Demo案例中  很明显两者是如下的关系:3......
  • day23 SpringCloud应用改造实践 (8.3.1-8.4.1)
    8.3-1-SpringCloud应用改造实践上一、使用SkyWalkingAgentJava中使用agent,提供以下三种方式实现使用官方提供的基础镜像skywalking-base将agent包构建到已经存在的基础镜像中sidecar模式挂载agent(推荐)1.1使用官方提供的基础镜像https://skywalking.apache.org/downlo......
  • SpringBoot+JaywayJsonPath实现Json数据的DSL(按照指定节点表达式解析json获取指定数
    场景若依前后端分离版手把手教你本地搭建环境并运行项目:若依前后端分离版手把手教你本地搭建环境并运行项目_前后端分离项目本地运行在上面搭建SpringBoot项目的基础上,并且在项目中引入fastjson、hutool等所需依赖后。JaywayJsonPath:GitHub-json-path/JsonPath:JavaJsonPathi......
  • 使用容器快速在阿里云 ECS 多节点上搭建 Citus 12.1 集群
    阿里云ECS机器节点这里我们使用两台同一区域的ECS机器。机器配置:2核2G。(ps:阿里云99元一年的活动)一台安装coordinator(协调器),这里内网IP为172.18.60.11一台安装worker,这里内网IP为172.18.60.12操作系统两台机器分别安装了厂商的AlibabaCloudLinu......
  • K8s中下线Hadoop节点(节点下线,调整副本数)
    K8s中下线Hadoop节点(节点下线,调整副本数)将Hadoop从三副本修改为双副本,同时修改datanode和nodemanager节点数为2修改hadoop节点副本数和datanode以及yarnnodemanager节点数:hadoop.hdfs.replication=2hadoop.hdfs.datanode=2hadoop.yarn.nodemanager=2重启hadoopyarn所有pod......
  • SpringCloud与Dubbo的区别(九)
    前言参看链接:https://blog.csdn.net/huangtenglong/article/details/131144602一、SpringCloud与Dubbo的区别初始定位不同:SpringCloud定位为微服务架构下的一站式解决方案;Dubbo是SOA时代的产物,它的关注点主要在于服务的调用和治理生态环境不同:SpringCloud依托于Spring平台,具......
  • 廊坊2023电信1控制节点重启
    环境:廊坊2023电信1环境负责人:潘义诚变更原因客户提出涉及主机: 重启步骤:职位平台随便启动这个就没有再用,没有影响。裸机平台我们这边说是104、106、108、110可以一起重启,启动好后再重启105、107、109、111。os-deploy和ceph-deploy直接重启就行。ntp-server和yum-server要分......
  • 浅聊SpringCloud的网关
    为什么要设计网关?上网搜罗了一下,觉得别人说的挺好,就引用了一下,在使用微服务的时候,不同的功能业务会集成一个服务群,而网关是基于服务群上的一个服务层,也是单独暴露给客户端的APIs。客户端对微服务的依赖直接使重构服务变得困难。一种直观的方法是将这些服务隐藏在一个新的服务层后面......