• SpringCloud中服务间通信方式以及Ribbon、Openfeign组件的使用


    7. 服务间通信方式

    接下来在整个微服务架构中,我们比较关心的就是服务间的服务改如何调用,有哪些调用方式?

    image-20200713095528763

    总结:在springcloud中服务间调用方式主要是使用 http restful方式进行服务间调用

    基于RestTemplate的服务调用

    # 0.说明
    - spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
    
    • 1
    • 2
    1. RestTemplate 服务调用
    # 1.创建两个服务并注册到consul注册中心中
    - users    代表用户服务 端口为 8888
    - orders   代表订单服务 端口为 9999
    	`注意:这里服务仅仅用来测试,没有实际业务意义
    
    • 1
    • 2
    • 3
    • 4

    image-20220904064504274

    两个服务首先要引入springboot-stater-web依赖,使它们成为一个springboot应用

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    为了使它们构建成consul的客户端,两个服务都要引入consul client以及consul健康检查依赖

    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-consul-discoveryartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    配置两个服务的 application.properties

    image-20220904072629057

    在它们的入口类分别加上 服务注册 client 注解

    image-20220904072744612

    # 2.在订单服务中提供服务方法
    
    • 1
    @RestController
    public class OrderController {
    
        @GetMapping("order")
        public String demo(){
            System.out.println("order demo ...");
            return "order demo OK!!!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    # 3.在用户服务中使用restTemplate进行调用
    
    • 1
    @RestController
    public class UserController {
    
        @GetMapping("user")
        public String invokeDemo(){
            System.out.println("user demo ...");
            // 1. 调用订单服务 服务地址:http://localhost:9999/order  无参  接收返回值String类型
            RestTemplate restTemplate = new RestTemplate(); // spring 提供的对象,相当于java中的浏览器
            // 参数1:订单服务的url   参数2:返回类型
            String orderResult = restTemplate.getForObject("http://localhost:9999/order", String.class);
            System.out.println(orderResult);
            return "调用order服务成功,结果为:" + orderResult;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    # 4.启动服务
    
    • 1

    image-20200713102320469

    image-20200713140350189

    # 5.测试服务调用
    - 浏览器访问用户服务 http://localhost:8888/user
    
    • 1
    • 2

    image-20220904072947827

    # 6.总结
    - rest Template是直接基于服务地址调用没有在服务注册中心获取服务,也没有办法完成服务的负载均衡如果需要实现服务的负载均衡需要自己书写服务负载均衡策略。
    
    • 1
    • 2

    RestTemplate是由java管理的,我们希望日后直接注入使用,建议写一个配置类,日后直接注入RestTemplate即可

    @Configuration      // 代表这是springboot中的一个配置类   spring.xml  工厂  创建对象
    public class BeansConfig {
    
        @Bean           // RestTemplate 是由java管理的,我们将其交给springboot管理
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    基于Ribbon的服务调用

    # 0.说明
    - 官方网址: https://github.com/Netflix/ribbon
    - Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
    
    • 1
    • 2
    • 3

    Ribbon不能发请求,它只能做负载均衡,真正发请求还是RestTemplate,Ribbon是做客户端的负载均衡。

    1.Ribbon 服务调用
    # 1.项目中引入依赖
    - 说明: 
    	1.如果使用的是eureka client 和 consul client,无须引入依赖,因为在eureka,consul中默认集成了ribbon组件
    	2.如果使用的client中没有ribbon依赖需要显式引入如下依赖
    
    • 1
    • 2
    • 3
    • 4
    
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-netflix-ribbonartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    # 2.查看consul client中依赖的ribbon
    
    • 1

    image-20200713140804414

    # 3.使用restTemplate + ribbon进行服务调用
    - 使用discoveryClient  进行客户端调用
    - 使用loadBalanceClient 进行客户端调用
    - 使用@loadBalanced     进行客户端调用
    
    • 1
    • 2
    • 3
    • 4
    # 3.1 使用discoveryClient形式调用
    
    • 1
    @Autowired
    private DiscoveryClient discoveryClient;
    
    //获取服务列表
    List<ServiceInstance> products = discoveryClient.getInstances("ORDERS"); // ORDERS是服务id
    for (ServiceInstance product : products) {
      log.info("服务主机:[{}]",product.getHost());
      log.info("服务端口:[{}]",product.getPort());
      log.info("服务地址:[{}]",product.getUri());
      log.info("====================================");
    }
    // 这里我们没有做负载均衡,我们可以搞个随机数自己做负载均衡
    String result = new RestTemplate().getForObject(services.get(0).getUri() + "/order", String.class);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    # 3.2 使用loadBalance Client形式调用
    
    • 1
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    //根据负载均衡策略选取某一个服务调用(默认轮询策略)
    ServiceInstance serviceInstance = loadBalancerClient.choose("服务ID");
    log.info("服务主机:[{}]",serviceInstance.getHost());
    log.info("服务端口:[{}]",serviceInstance.getPort());
    log.info("服务地址:[{}]",serviceInstance.getUri());
    String result2 = new RestTemplate().getForObject(serviceInstance.getUri() + "/order", String.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    # 3.3 使用@loadBalanced
    
    • 1
    //1.整合restTemplate + ribbon
    @Configuration      // 代表这是springboot中的一个配置类   spring.xml  工厂  创建对象
    public class BeansConfig {
    
        @Bean           // RestTemplate 是由java管理的,我们将其交给springboot管理
        @LoadBalanced   // 使RestTemplate具有Ribbon特性,即负载均衡
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    
    
    
    //2.调用服务位置注入RestTemplate
    @Autowired
    private RestTemplate restTemplate;
    //3.调用
    String result3 = restTemplate.getForObject("http://ORDERS/order", String.class);
    // ORDERS是服务id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    2.Ribbon负载均衡策略
    # 1.ribbon负载均衡算法
    - RoundRobinRule         		轮训策略	按顺序循环选择 Server
    - RandomRule             		随机策略	随机选择 Server
    - AvailabilityFilteringRule 可用过滤策略
     	`会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
    
    - WeightedResponseTimeRule  响应时间加权策略   
    	`根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用		
    		RoundRobinRule策略,等统计信息足够会切换到
    
    - RetryRule                 重试策略          
    	`先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
    	
    - BestAviableRule           最低并发策略     
    	`会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image-20200713162940968

    3.修改服务的默认负载均衡策略
    # 1.修改服务默认随机策略(在服务调用方的配置文件处)
    - 服务id.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
    	`下面的products为服务的唯一标识
    上面的服务id指的是调用哪个服务的服务id,比如user调用order,那就在user的配置文件处写order的服务id
    后面写全限定名,如果想要使用其它策略直接查询它的全限定名然后设置即可。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ORDERS.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
    
    • 1

    image-20220904145217081

    4.Ribbon停止维护
    # 1.官方停止维护说明
    - https://github.com/Netflix/ribbon
    
    • 1
    • 2

    image-20200713195706787


    8.OpenFeign组件的使用

    • 思考: 使用RestTemplate+ribbon已经可以完成对端的调用,为什么还要使用feign?
    String restTemplateForObject = restTemplate.getForObject("http://服务名/url?参数" + name, String.class);
    
    • 1
    # 存在问题:
    - 1.每次调用服务都需要写这些代码,存在大量的代码冗余
    - 2.服务地址如果修改,维护成本增高
    - 3.使用时不够灵活
    
    • 1
    • 2
    • 3
    • 4

    OpenFeign 组件

    # 0.说明
    - https://cloud.spring.io/spring-cloud-openfeign/reference/html/
    - Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性(可以使用springmvc的注解),可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,默认实现了负载均衡的效果并且springcloud为feign添加了springmvc注解的支持。
    
    • 1
    • 2
    • 3

    为了不影响之前的创建的项目,新创建两个项目springcloud_06categoryspringcloud_07product,并引入相关依赖,并编写配置文件以及在入口类上加上相应注解(@SpringBootApplication、@EnableDiscoveryClient)

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-consul-discoveryartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1.openFeign 服务调用
    # 1.服务调用方引入依赖OpenFeign依赖
    比如 类别服务 调用 商品服务,类别服务就是服务调用方
    
    • 1
    • 2
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    # 2.入口类加入@EnableFeignClients注解开启OpenFeign支持
    
    • 1
    @SpringBootApplication
    @EnableDiscoveryClient      // 开启服务注册
    @EnableFeignClients         // 开启openfeign客户端调用
    public class CategoryApplication {
        public static void main(String[] args) {
            SpringApplication.run(CategoryApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    # 3.创建一个客户端调用接口(调用哪个服务接口名字就写成服务名,类别服务调用商品服务如下)
    
    • 1
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 调用商品服务这个路径的服务
        @GetMapping("/product")
        String product();  // 在商品服务里有具体路径,我们在类别服务里写这个接口方法(返回值、形参列表、路径一致即可,方法名字无所谓)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image-20200713202133954

    # 4.使用feignClient客户端对象调用服务
    
    • 1
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            String product = productClient.product();       // 调用商品服务
            return "category ok !!!" + "调用" + product;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    # 5.访问并测试服务
    - http://localhost:8787/category
    
    • 1
    • 2

    image-20220904210631629

    2.调用服务并传参
    # 0.说明
    - 服务和服务之间通信,不仅仅是调用,往往在调用过程中还伴随着参数传递,接下来重点来看看OpenFeign在调用服务时如何传递参数
    
    • 1
    • 2
    零散类型参数传递

    零散类型参数传递分为两种,一种是 ? 形式传参,另一种是路径传参

    url?name=xiaochen&age=23

    url/xiaochen/23

    类别服务调用商品服务,类别服务就是调用方,商品服务就是被调用方
    
    # 调用方在Openfeign中进行零散类型参数传递时必须给参数加入注解
    
    1. querystring方式传递参数:    ?name=xiaochen&age=23
    	 注意:在openfeign接口声明中必须给参数加入注解@RequestParam,并且这个注解value属性值一定要写
    
    2. 路径传递参数:                ur1/xiaochen/23
    	 注意:在openfeign接口声明中必须给参数加入注解@PathVariable
    	 
    # 在被调用方的服务中
    
    在商品服务的controller方法中,对于零散类型参数传递,默认是 ? 传参,参数不需要加 @RequestParam注解
    													  但是如果是路径传递参数,参数则需要加@PathVariable注解
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    // 商品服务中添加方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 定义一个接收零散类型参数接口, 路径传递参数
        @GetMapping("/test1/{id}/{name}")
        public String test1(@PathVariable("id") Integer id, @PathVariable("name") String name){
            // 默认是?接收,路径接收的话方法参数需要加@PathVariable注解
            System.out.println("id = " + id);
            System.out.println("name = " + name);
            return "test1 ok, 当前服务端口为:" + port;
        }
    
        // 定义一个接受零散类型参数的方法 queryString  ?号传参
        @GetMapping("/test")
        public String test(String name, Integer age){
            System.out.println("name = " + name);
            System.out.println("age = " + age);
            return "test ok, 当前服务端口为:" + port;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    // 类别服务中声明方法
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明调用商品服务的test1路径方法,路径传递参数
        @GetMapping("/test1/{id}/{name}")
        String test1(@PathVariable("id") Integer id, @PathVariable("name") String name);
    
        // 声明调用商品服务的test路径方法,参数:name,age
        @GetMapping("/test")
        String test(@RequestParam("name") String name, @RequestParam("age") Integer age);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // 类别服务中调用商品服务
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            
            String name = "xiaochen";
            Integer age = 23;
            productClient.test(name, age);
            Integer id = 21;
            name = "zhangsan";
            String product = productClient.test1(id, name);       // 调用商品服务
            return "category ok !!!" + "调用" + product;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    对象类型参数传递
    类别服务调用商品服务,类别服务就是调用方,商品服务就是被调用方
    
    # 在调用方Openfeign传递对象时必须指明以什么形式接收(form表单还是json格式)
    
    1. form表单形式(这个必须涉及到文件上传时才可以)
    		form表单形式传递必须加@RequestPart注解,并且注解value属性得有值,将表单形式数据转换成对象,这个
    		注解只能配合post、put、pach用
    
    2. 接收json格式
    		json格式传递必须加@RequestBody注解,会在接收json格式数据时转换成对象
    		
    # 在被调用方中
    
    1. form表单形式
    		在controller中默认就是form形式,不需要加@RequestPart注解
    
    2. json格式
    		在controller中如果接收json格式数据,参数要加@RequestBody注解,将json转为对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    // 商品服务中添加方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 定义一个接收对象类型参数接口
        @PostMapping("/test2")  // 传对象一般都是Post方法
        public String test2(@RequestBody Product product){
            System.out.println("product = " + product);
            return "test2 ok, 当前服务端口为:" + port;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    // 类别服务中声明接口方法
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明调用商品服务中test2方法 传递一个商品对象
        @PostMapping("/test2")
        String test2(@RequestBody("product") Product product);  // 指定是以form表单的形式传递还是以json形式传
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    // 类别服务中调用
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            String result = productClient.test2(new Product(1, "小吃", 23.23, new Date()));
            return "category ok !!!" + "调用" + result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    数组类型参数传递
    类别服务调用商品服务,类别服务就是调用方,商品服务就是被调用方
    
    # 调用方在Openfeign中进行数组参数传递时必须给参数加入注解@RequestParam
    数组的方式只能是通过 ?传参,没有办法在路径中传递
    /test3?ids=21&ids=22
    在openfeign接口声明中必须给参数加入注解@RequestParam,并且这个注解value属性值一定要写
    
    # 在被调用方的服务中
    在商品服务的controller方法中,对于数组参数传递,参数可以加可以不加 @RequestParam注解
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    // 商品服务中的接收数组类型的controller方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 定义个接口接收数据类型参数
        @GetMapping("/test3")
        public String test3(String[] ids){
            for (String id : ids) {
                System.out.println(id);
            }
            return "test3 ok, 当前服务端口为:" + port;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    // 类别服务中声明接口方法
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
        // 声明调用商品服务中test3接口 传递一个数组类型  /test3?ids=21&ids=22
        @GetMapping("/test3")
        String test3(@RequestParam("ids") String[] ids);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    // 类别服务中调用
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            String result = productClient.test3(new String[]{"21", "23", "24"});
            return "category ok !!!" + "调用" + result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    集合类型参数传递

    springmvc中不能直接传递集合类型,我们应该将其封装成一个对象,然后接收对象

    // 定义用来封装集合类型参数的对象
    public class CollectionVO {
    
        private List<String> ids;
    
        public List<String> getIds() {
            return ids;
        }
    
        public void setIds(List<String> ids) {
            this.ids = ids;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    类别服务调用商品服务,类别服务就是调用方,商品服务就是被调用方
    
    # 调用方在Openfeign中进行集合参数传递时必须给参数加入注解@RequestParam组织成 ids=xx&idx=yy 形式
    /test3?ids=21&ids=22
    在openfeign接口声明中必须给参数加入注解@RequestParam,并且这个注解value属性值一定要写
    
    # 在被调用方的服务中
    在商品服务的controller方法中,对于集合参数传递,应该接收封装集合的对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // 被调用方的controller方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 定义一个接口接收集合类型参数        springmvc不能直接接收集合类型参数,如果想接收集合类型的参数,需要封装到对象里,然后接受这个对象
        @GetMapping("/test4")           // 必须将集合放入对象中使用对象的方式接受才行
        public String test4(CollectionVO collectionVO){
            collectionVO.getIds().forEach(id -> System.out.println(id));
            return "test4 ok, 当前服务端口为:" + port;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    // 调用方的Openfeign接口中
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明调用商品服务中test4接口 传递一个List集合类型参数    
        @GetMapping("/test4")
        String test4(@RequestParam("ids") String[] ids); 
        //  使用@RequestParam 组织成  /test4?ids=21&ids=22 形式
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    // 调用方调用
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            String result = productClient.test4(new String[]{"21", "23", "24"});
            return "category ok !!!" + "调用" + result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    GET方式调用服务传递参数
    # 1.GET方式调用服务传递参数
    - 在商品服务中加入需要传递参数的服务方法来进行测试
    - 在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
    
    • 1
    • 2
    • 3
    // 1.商品服务中添加如下方法
     @GetMapping("/product/findOne")
    public Map<String,Object> findOne(String productId){
      log.info("商品服务查询商品信息调用成功,当前服务端口:[{}]",port);
      log.info("当前接收商品信息的id:[{}]",productId);
      Map<String, Object> map = new HashMap<String,Object>();
      map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port);
      map.put("status",true);
      map.put("productId",productId);
      return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20200713203833730

    //2.用户服务中在product客户端中声明方法
    @FeignClient("PRODUCTS")
    public interface ProductClient { 
    	@GetMapping("/product/findOne")
     	String findOne(@RequestParam("productId") String productId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image-20200713204301830

    //3.用户服务中调用并传递参数
    //注入客户端对象
    @Autowired
    private ProductClient productClient;
    
    @GetMapping("/user/findAllFeignClient")
    public String findAllFeignClient(){
      log.info("通过使用OpenFeign组件调用商品服务...");
      String msg = productClient.findAll();
      return msg;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20200713204359700

    # 测试访问
    
    • 1

    image-20200713204827577

    image-20200713204851383

    post方式调用服务传递参数
    # 2.post方式调用服务传递参数
    - 在商品服务中加入需要传递参数的服务方法来进行测试
    - 在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
    
    • 1
    • 2
    • 3
    //1.商品服务加入post方式请求并接受name
    @PostMapping("/product/save")
    public Map<String,Object> save(String name){
      log.info("商品服务保存商品调用成功,当前服务端口:[{}]",port);
      log.info("当前接收商品名称:[{}]",name);
      Map<String, Object> map = new HashMap<String,Object>();
      map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port);
      map.put("status",true);
      map.put("name",name);
      return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20200713205125242

    //2.用户服务中在product客户端中声明方法
    //value属性用来指定:调用服务名称
    @FeignClient("PRODUCTS")
    public interface ProductClient {
        @PostMapping("/product/save")
        String save(@RequestParam("name") String name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20200713205734920

    //3.用户服务中调用并传递参数
    @Autowired
    private ProductClient productClient;
    
    @GetMapping("/user/save")
    public String save(String productName){
      log.info("接收到的商品信息名称:[{}]",productName);
      String save = productClient.save(productName);
      log.info("调用成功返回结果: "+save);
      return save;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20200713205823467

    # 测试访问
    
    • 1

    image-20200713205919054

    image-20200713210001477

    # 2.传递对象类型参数
    - 商品服务定义对象
    - 商品服务定义对象接收方法
    - 用户服务调用商品服务定义对象参数方法进行参数传递
    
    • 1
    • 2
    • 3
    • 4
    //1.商品服务定义对象
    @Data
    public class Product {
        private Integer id;
        private String name;
        private Date bir;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20200713210437488

    //2.商品服务定义接收对象的方法
    @PostMapping("/product/saveProduct")
    public Map<String,Object> saveProduct(@RequestBody Product product){
      log.info("商品服务保存商品信息调用成功,当前服务端口:[{}]",port);
      log.info("当前接收商品名称:[{}]",product);
      Map<String, Object> map = new HashMap<String,Object>();
      map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port);
      map.put("status",true);
      map.put("product",product);
      return map;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20200713210641668

    //3.将商品对象复制到用户服务中
    //4.用户服务中在product客户端中声明方法
    @FeignClient("PRODUCTS")
    public interface ProductClient {
      @PostMapping("/product/saveProduct")
      String saveProduct(@RequestBody Product product);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20200713211213241

    // 5.在用户服务中调用保存商品信息服务
    //注入客户端对象
    @Autowired
    private ProductClient productClient;
    
    @GetMapping("/user/saveProduct")
    public String saveProduct(Product product){
      log.info("接收到的商品信息:[{}]",product);
      String save = productClient.saveProduct(product);
      log.info("调用成功返回结果: "+save);
      return save;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20200713211308524

    # 测试
    
    • 1

    image-20200713211338475

    image-20200713211402844

    3. Openfeign调用服务响应处理
    返回类型为对象类型

    类别服务调用商品服务

    // 1. 商品服务中的方法返回类型为对象
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 定义一个接口接收id类型参数,返回一个基于id查询的对象
        @GetMapping("/product/{id}")
        public Product product(@PathVariable("id") Integer id){
            System.out.println("id = " + id);
            return new Product(id, "小陈", 23.23, new Date());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    // 2. 类别服务中声明对应的接口方法
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明根据id查询商品信息的接口
        @GetMapping("/product/{id}")
        Product product(@PathVariable("id") Integer id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 3. 类别服务中调用
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public Product category(){
            System.out.println("category service ...");
            Product product = productClient.product(23);
            return product;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    返回类型为集合类型

    类别服务调用商品服务

    // 1. 商品服务中的controller方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 根据id查询集合并返回
        @GetMapping("/products")
        public List<Product> findById(Integer id){
            System.out.println("id = " + id);
            // 调用业务逻辑根据id查询商品列表(集合)
            List<Product> products = new ArrayList<>();
            products.add(new Product(1, "小陈", 23.23, new Date()));
            products.add(new Product(2, "小明", 23.23, new Date()));
            products.add(new Product(3, "小亮", 23.23, new Date()));
            return products;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    // 2. 类别服务中声明接口方法
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明调用商品服务根据类别id查询一组商品服务
        @GetMapping("/products")
        List<Product> findById(@RequestParam("id") Integer id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 3. 类别服务中调用商品服务
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public List<Product> category(){
            System.out.println("category service ...");
            List<Product> products = productClient.findById(1);
            products.forEach(product -> System.out.println(product));
            return products;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    返回类型为map类型

    类别服务调用商品服务

    如果返回类型为map,在业务中map定义一般是 Map,key是String,value是Object,比如我们的类别服务调用商品服务的话,返回一个map,如果想在类别服务中处理一下map,因为数据在处理时只能由子类转为父类,不能由父类转为子类,所以我们要自定义序列化,先将Object转为json,再自定义将json转为指定类型数据。其中我们需要用到json类,需要引入下面fastjson依赖

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.76version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // 1. 商品服务中controller方法
    
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String port;
    
        // 根据id查询列表并返回集合和总条数
        @GetMapping("/select/{id}")
        public Map<String, Object> findAll(@PathVariable("id") Integer id){
            Map<String, Object> map = new HashMap<>();
            List<Product> products = new ArrayList<>();
            products.add(new Product(1, "小陈", 23.23, new Date()));
            products.add(new Product(2, "小明", 23.23, new Date()));
            products.add(new Product(3, "小亮", 23.23, new Date()));
            map.put("rows", 100);  // 总条数
            map.put("products", products); // 产品列表
            return map;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    // 2. 类别服务中声明Openfeign接口方法
    
    // 调用商品服务的接口
    @FeignClient(value = "PRODUCT")      // value: 用来书写调用哪个服务的服务id
    public interface ProductClient {
    
        // 声明调用商品服务根据id查询返回map
        // 根据id查询列表并返回集合和总条数
        @GetMapping("/select/{id}")
        public String findAll(@PathVariable("id") Integer id);
    		/*
    		注意:虽然商品服务中返回是map,但是我们的类别服务中返回设置为String,这样方便后面
    				 自定义序列化(map可以转为String,调用它的toString方法)
    		*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    // 3. 类别服务中调用商品服务方法
    
    @RestController
    public class CategoryController {
    
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/category")
        public String category(){
            System.out.println("category service ...");
            String result = productClient.findAll(23);
    
            //  对象转为序列化  字符串转为json
            JSONObject jsonObject = JSONObject.parseObject(result); // JSONObject底层实现了map
            System.out.println(jsonObject.get("rows"));
            Object rows = jsonObject.get("products");
            System.out.println(rows);
    
            // 二次json序列化   json转为对象  自定义json序列化
            List<Product> products = JSONObject.parseArray(rows.toString(), Product.class);
            products.forEach(product -> System.out.println(product));
    
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    4.OpenFeign超时设置
    # 0.超时说明
    - 默认情况下,openFiegn在进行服务调用时,要求服务提供方处理业务逻辑时间必须在1S内返回,如果超过1S没有返回则OpenFeign会直接报错,不会等待服务执行,但是往往在处理复杂业务逻辑是可能会超过1S,因此需要修改OpenFeign的默认服务调用超时时间。
    - 调用超时会出现如下错误:
    
    • 1
    • 2
    • 3
    # 1.模拟超时
    - 服务提供方加入线程等待阻塞
    
    • 1
    • 2

    image-20200713213322984

    # 2.进行客户端调用
    
    • 1

    image-20200713213415230

    # 3.修改OpenFeign默认超时时间
    
    • 1

    比如类别服务调用商品服务时,在类别服务的配置文件中修改使用Openfeign调用商品服务的超时时间

    #配置指定服务连接超时 单位:毫秒   注:PRODUCT是商品服务的id
    feign.client.config.PRODUCT.connectTimeout=5000  		
    #配置指定服务等待超时 单位:毫秒
    feign.client.config.PRODUCT.readTimeout=5000		 
    #配置调用所有服务连接超时
    #feign.client.config.default.connectTimeout=5000  
    #配置调用所有服务等待超时
    #feign.client.config.default.readTimeout=5000			
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    5.OpenFeign调用详细日志展示
    # 0.说明
    - 往往在服务调用时我们需要详细展示feign的日志,默认feign在调用时并不是最详细日志输出,因此在调试程序时应该开启feign的详细日志展示。feign对日志的处理非常灵活,可以为每个feign客户端指定日志记录策略,每个客户端都会默认创建一个logger,默认情况下logger的名称是feign的全限定名,需要注意的是,feign日志的打印只会DEBUG级别做出响应。
    - 我们可以为feign客户端配置各自的logger.lever对象,告诉feign记录那些日志logger.lever有以下的几种值
    
    	`NONE`  不记录任何日志
    	`BASIC` 仅仅记录请求方法,url,响应状态代码及执行时间
    	`HEADERS` 记录Basic级别的基础上,记录请求和响应的header
    	`FULL` 记录请求和响应的header,body和元数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    # 1.开启日志展示
    
    • 1

    比如类别服务(CATEGORY)调用商品服务(PRODUCT),在类别服务配置文件中加上下面配置

    #开启Openfeign中调用商品服务日志展示   PRODUCT是服务名
    feign.client.config.PRODUCT.loggerLevel=full  
    #开启对所有服务日志展示
    #feign.client.config.default.loggerLevel=full
    #指定feign调用客户端对象所在的包,必须是debug级别,com.baizhi是包名   feignclients是固定
    logging.level.com.baizhi.feignclients=debug
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # 2.测试服务调用查看日志
    
    • 1

    image-20200713215108861

  • 相关阅读:
    Git代码上传
    fan-shaped hole的孔倾角越大,流量系数是越大还是越小?
    gRPC 协议缓冲区
    Linux常用命令
    如何为开源项目和社区做贡献 -- 你应该知道的十件事
    与AP、IB、预科相比,A-Level有哪些优势?
    CodeForces Round #501 (div.3) A~E2
    Android studio自定义输出编译apk的名称
    新鲜出炉的 yoloV5可视化实战项目(1)
    03-背景属性
  • 原文地址:https://blog.csdn.net/qq_50313418/article/details/126714128