上节说到ribbon+resttemplate不灵活,而且有一点没说,ribbon和eureka一样,2.X耐非也不维护了,只有1.X会继续活跃。所以就引出接下来要使用的openFeign了。

Feign是springcloud-spring公司的东西,贴下概念:

1.PNG

在代码里声明也比较简单,在做注册中心时写代码需要三步,分别是:引依赖、写配置、加注解;加入openFeign组件大同小异,需要:引依赖、加注解(启动类中声明)、写接口并在接口中加注解,配置文件的话如果加超时处理或者日志记录等的话也需要写一下,剩下的就是业务层代码的实现了,归功于注解,以前的ribbon和resttemplate相关的代码也不用写了,只需要关于业务代码实现就行了。走一下实现流程:

实现流程

首先创建两个springboot项目类别服务和商品服务并注册到服务中心,由于类别服务需要调用商品服务的接口完成服务间通信,所以类别服务相当于商品服务的client端(服务调用方),openFeign的操作基本都在类别服务中

简单4步,client的openFeign的调用就写好了:

1、pom引入openFeign依赖
<!--Open Feign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2、启动类中加入注解 开启client端Feign的调用支持
@EnableFeignClients      //开启openfeign客户端调用

3、开发client端接口(主要是要下面这个注解)
//调用商品服务接口
@FeignClient("PRODUCT")        //value: 用来书写调用服务服务id

4、接口中调用商品服务(这里的接口路径就是商品服务的接口路径,因为第三步声明的serverid就相当于是去访问
http://ip:port/PRODUCT(商品服务的注册服务名称)/product)
//调用商品服务
    @GetMapping("/product")
    public String product();

注意接口只是声明,并不写实际业务代码,业务代码在商品服务里写,而且接口中方法声明也要严格一致

具体实现就是 类别服务Controller->接口->商品服务Controller

缕清了逻辑,然后就要看参数的传递和响应处理了

传参和返回

传参分为:1、传递零散参数 2、传递对象 3、传递数组或者集合

传递零散参数有queryString和路径传参的方式,平常用的也比较多,有一点要注意那就是在queryString中如果要传递多参数,参数前一定要加@RequestParam,不,其实queryString都要加,甭管你传几个参数,具体原因看下头第一个注释

//声明调用商品服务中test?name=xxx&age=xxx接口传递name,age
  @GetMapping("/test")
//    public String test(String name,Integer age);     //因为底层不知道怎么组织(拼)参数(一个参数可以),所以这样写报java.lang.IllegalStateException: Method has too many Body parameters
//    public String test(String name);               //一个参数可以,但也不能这么写!
    public String test(@RequestParam("name") String name, @RequestParam("age") Integer age);

路径传参:

//声明调用商品服务中test1接口 路径传递数据
    @GetMapping("/test1/{id}/{name}")
    public String test1(@PathVariable("id") Integer id, @PathVariable("name") String name);

传递对象也比较简单啦,喜闻乐见用经常用的@RequestBody传就完了,实体类getset toString 构造啥的都写上就完事了

//声明调用商品服务中test2接口 传递一个商品对象
    @PostMapping("/test2")
    public String test2(@RequestBody Product product);

传数组也比较简单,在url中参数名一样,值不一样就行,所以也可以用@RequestParam来传

    //声明调用商品服务中test3接口 传递一个数组类型 queryString /test3?ids=21&ids=22
    @GetMapping("/test3")
    public String test3(@RequestParam("ids") String[] ids);

传集合,能不用就不用了,传参方式和传数组一样,但实际上在商品服务该接口的接参接的是个VO(不做深究)

    //声明调用商品服务中test4接口 传递一个list集合类型参数 /test4?ids=21&ids=22
    @GetMapping("/test4")
    public String test4(@RequestParam("ids") String[] ids);    //能不用集合就不用集合了,直接用数组

响应返回:

一般做业务处理返回很少有返回String型的吧,下面三个分别返回了对象、集合和map,数据处理这块没啥好说的了,一定要灵活、灵活(我习惯全转json)。。

    //声明调用商品服务根据类别id查询分别查询商品信息 以及总条数
    @GetMapping("/productList")
    public Map<String,Object> findByCategoryIdAndPage(@RequestParam("page") Integer page, @RequestParam("rows") Integer rows, @RequestParam("categoryId") Integer categoryId);

    //声明调用根据id查询商品信息接口
    @GetMapping("/products")
    public List<Product> findByCategoryId(@RequestParam("categoryId") Integer categoryId);

    //声明调用根据id查询商品信息接口
    @GetMapping("/product/{id}")
    public Product product(@PathVariable("id") Integer id);

以上就是数据的传参和返回,然后把商品服务和类别服务的Controller都贴出来

package com.jin.controller;

import com.jin.entity.Product;
import com.jin.feignclient.ProductClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author jinyunlong
 * @date 2021/7/12 16:50
 * @profession ICBC锅炉房保安
 */
@RestController
public class CategoryController {

    @Autowired
    private ProductClient productClient;

    @GetMapping("/category")
    public String category(){
        System.out.println("category service.....");
        //1.RestTemplate 2.RestTemplate+Ribbon 3.OpenFeign
//        String product = productClient.product();
//        String list = productClient.list();
//
//        System.out.println("结果1:"+product);
//        System.out.println("结果2:"+list);
//        String product = productClient.test("jin",25);
//        String product = productClient.test1(25,"jin");

//        String product = productClient.test2(new Product(1,"jin",200.2,new Date()));
//        Product product1 = new Product();
//        product1.setId(22);
//        product1.setName("jin");
//        product1.setPrice(200.22);
//        String product = productClient.test2(product1);
//        String product = productClient.test3(new String[]{"21","22","23"});
//        String product = productClient.test4(new String[]{"21","22","23"});

//        Product product = productClient.product(21);
//        List<Product> product = productClient.findByCategoryId(1);
//        for (int i = 0; i < product.size(); i++) {
//            System.out.println(product.get(i));
//        }
//        Map<String,Object> product = productClient.findByCategoryIdAndPage(1,2,3);

        //超时测试
        String product = productClient.product();  //配置文件超时时间设为5s,商品类接口睡眠时间设为2s,所以可以正常响应


//        return "category ok!"+product;
        return product.toString();
    }
}

package com.jin.feignclient;

import com.jin.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 * @author jinyunlong
 * @date 2021/7/12 17:05
 * @profession ICBC锅炉房保安
 */
//调用商品服务接口
@FeignClient("PRODUCT")        //value: 用来书写调用服务服务id
public interface ProductClient {

    //声明调用商品服务根据类别id查询分别查询商品信息 以及总条数
    @GetMapping("/productList")
    public Map<String,Object> findByCategoryIdAndPage(@RequestParam("page") Integer page, @RequestParam("rows") Integer rows, @RequestParam("categoryId") Integer categoryId);

    //声明调用根据id查询商品信息接口
    @GetMapping("/products")
    public List<Product> findByCategoryId(@RequestParam("categoryId") Integer categoryId);

    //声明调用根据id查询商品信息接口
    @GetMapping("/product/{id}")
    public Product product(@PathVariable("id") Integer id);

    //声明调用商品服务中test4接口 传递一个list集合类型参数 /test4?ids=21&ids=22
    @GetMapping("/test4")
    public String test4(@RequestParam("ids") String[] ids);    //能不用集合就不用集合了,直接用数组

    //声明调用商品服务中test3接口 传递一个数组类型 queryString /test3?ids=21&ids=22
    @GetMapping("/test3")
    public String test3(@RequestParam("ids") String[] ids);
    //声明调用商品服务中test2接口 传递一个商品对象
    @PostMapping("/test2")
    public String test2(@RequestBody Product product);

    //声明调用商品服务中test1接口 路径传递数据  /url/id/name
    @GetMapping("/test1/{id}/{name}")
    public String test1(@PathVariable("id") Integer id, @PathVariable("name") String name);
    //声明调用商品服务中test?name=xxx&age=xxx接口传递name,age
    @GetMapping("/test")
//    public String test(String name,Integer age);     //因为底层不知道怎么组织(拼)参数(一个参数可以),所以这样写报java.lang.IllegalStateException: Method has too many Body parameters
//    public String test(String name);               //一个参数可以,但也不能这么写!
    public String test(@RequestParam("name") String name, @RequestParam("age") Integer age);

    //调用商品服务
    @GetMapping("/product")
    public String product();

    @GetMapping("/list")
    public String list();
}

package com.jin.controller;

import com.jin.entity.Product;
import com.jin.vos.CollectionVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.*;

/**
 * @author jinyunlong
 * @date 2021/7/12 16:47
 * @profession ICBC锅炉房保安
 */
@RestController
public class ProductController {

    @Value("${server.port}")
    private int port;

    //查分页
    @GetMapping("/productList")
    public Map<String,Object> findByCategoryIdAndPage(Integer page,Integer rows,Integer categoryId){
        //根据类别id分页查询符合当前页集合数据       List<Product>  select * from t_product where categoryId=? limit ?(page-1)*rows,?(rows)
        //根据类别id查询当前类别下总条数            totalCount     select count(id) from t_product where categoryId=?
        //将查出的集合数据和总条数放入map中
        Map<String,Object> map = new HashMap<>();
        List<Product> products = new ArrayList<>();
        products.add(new Product(1,"qwer",23.3,new Date()));
        products.add(new Product(2,"tyui",23.3,new Date()));
        products.add(new Product(3,"op",23.3,new Date()));
        int total = 1000;
        map.put("rows",products);
        map.put("total",total);
        return map;
    }

    @GetMapping("/products")
    public List<Product> findByCategoryId(Integer categoryId){
        System.out.println("类别id:"+categoryId);
        //调用业务逻辑根据类别id查询商品列表
        List<Product> products = new ArrayList<>();
        products.add(new Product(1,"qwer",23.3,new Date()));
        products.add(new Product(2,"tyui",23.3,new Date()));
        products.add(new Product(3,"op",23.3,new Date()));
        return products;
    }

    //定义一个接口接收id类型参数,返回一个基于id查询的对象
    @GetMapping("/product/{id}")
    public Product product(@PathVariable("id") Integer id){
        System.out.println("id:"+id);
        return new Product(id,"kkkkkk",23.23,new Date());
    }

    //定义一个接口接受集合类型参数   springmvc 不能直接接受集合类型参数,如果想要接受集合类型参数必须将集合放入对象中,使用对象的方式接收才行
    //oo:oriented(面向) object(对象) 面向对象  vo(value object): 用来传递数据对象称之为值对象 dto:(data transfor(传输) object):数据传输对象
    @GetMapping("/test4")
    public String test4(CollectionVO collectionVO){
        for (String id: collectionVO.getIds()
             ) {
            System.out.println("id:"+id);
        }
        return "test4 ok,当前服务端口为"+port;
    }

    //定义一个接口接受数组类型参数
    @GetMapping("/test3")
    public String test3(String[] ids){
        for (int i = 0; i < ids.length; i++) {
            System.out.println("id:"+ids[i]);
        }
        //手动转为list
//        List<String> strings = Arrays.asList(ids);
        return "test3 ok,当前服务端口为"+port;
    }

    //定义一个接受对象类型参数接口
    @PostMapping("/test2")
    public String test2(@RequestBody Product product){
        System.out.println("product:"+product.toString());
        return "test2 ok,当前服务端口为"+port;
    }

    //定义一个接受零散参数类型接口  路径参数传递
    @GetMapping("/test1/{id}/{name}")
    public String test1(@PathVariable("id") Integer id,@PathVariable("name") String name){
        System.out.println("接收参数:"+id+name);
        return "test1 ok,当前服务端口为"+port;
    }

    //定义一个接受零散类型参数接口
    @GetMapping("/test")
    public String test(String name,Integer age){
        System.out.println("接收参数:"+name+age);
        return "test ok,当前服务端口为"+port;
    }

    @GetMapping("/product")
    public String product(){
        //超时测试
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("进入商品服务");
        return "product ok,当前提供服务端口:"+port;
    }

    @GetMapping("/list")
    public String list(){
        System.out.println("进入list服务");
        return "list ok,当前提供服务端口:"+port;
    }

}

可以看见商品服务最上面三个接口有简单的业务类代码了,返回类型也各不相同。

超时处理和日志使用

2.PNG

默认是1s,所以我在商品服务的product接口中 sleep了2s,发现果然访问不到了,修改默认超时时间只需要在配置文件里加配置就行,修改成5s后可以正常访问:

#修改openFeign超时时间  配置类别调用商品服务openfeign默认超时时间  默认时间1s
feign.client.config.PRODUCT.connect-timeout=5000
feign.client.config.PRODUCT.read-timeout=5000

#修改openFeign默认调用所有服务超时时间
#feign.client.config.default.connect-timeout=5000           #配置所有服务连接超时
#feign.client.config.default.read-timeout=5000              #配置所有服务等待超时

最后,要使用openFeign的日志的话,也只需要在配置文件中完成:

#开启openFeign中调用商品服务日志展示
feign.client.config.PRODUCT.loggerLevel=full
#展示openfeign日志
logging.level.com.jin.feignclient=debug

标题:再战SpringCloud(4)
作者:jyl
地址:http://jinyunlong.xyz/articles/2021/07/15/1626335070524.html