OpenFeign组件

518 阅读4分钟

OpenFeign组件的使用

遇到的问题

1)P28-(11:27),为什么要使用OpenFeign代替Ribbon完成服务间的通信。(46:00),解决集群的问题,图1。 2)P29-(10:30),。(14:30),类别服务调用商品服务,传递多个参数。(35:30),@PostMapping。 3)P30-(22:03),接收集合类型的参数,vo:用来传递数据的对象称为值对象。 4)P31-(29:00),响应处理返回的JSON字符串,怎么转换成想要的对象?需要用到自定义格式解析。 5)P32-19:30,OpenFeign的日志展示。

1.RestTemplate存在的问题:①路径写死。②不能自动转换响应结果为对应的对象。③必须集成Ribbon实现负载均衡。

2.OpenFeign能够解决RestTemplate存在的问题,实现系统间服务的通信问题。Feign默认集成了Ribbon,实现请求的负载均衡。使用简单:①只需要写一个接口+一个注解。②调用服务代码更加简单,自动完成数据传递过程中的对象转换。

3.服务间通信问题:①http协议:使用rest方式通信,效率低,但解耦合,推荐。②RPC技术:使用传输层协议通信,效率高,Dubbo框架。 4BD@7K9$IDJXIEX(UR{ZRXV.png

开发步骤

1)创建两个独立的SpringBoot应用,并注册到服务注册中心 consul。2)引入服务注册中心的依赖。3)修改配置文件。4)入口类加入注解。5)使用openfeign进行调用,①在服务调用方引入openfeign依赖,②在服务调用方入口类加入注解,开启Feign的调用支持。③开发客户端接口。

pom.xml

<parent>
    <artifactId>springcloud_parent</artifactId>
    <groupId>com.jun</groupId>
    <version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sc06_category</artifactId>
<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--consul-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <!--actuator-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--OpenFeign依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
</dependencies>

application.properties

server.port=8787
spring.application.name=CATEGORY
#注册到consul server
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500

# 配置类别调用商品服务openfeign默认超时时间,单位毫秒
#feign.client.config.PRODUCT.connectTimeout=5000 #配置指定服务连接超时
#feign.client.config.PRODUCT.readTimeout=5000 #配置指定服务等待超时

# 配置openfeign默认调用所有服务的超时时间
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000

# 开启openfeign中调用商品服务日志展示
feign.client.config.PRODUCT.loggerLevel=full
feign.client.config.ORDER.loggerLevel=none

# 展示openfeign日志
logging.level.com.jun.feignclient=debug

#---------------------------------------
server.port=8788
spring.application.name=PRODUCT
#注册到consul server
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
#mybatis
#redis
#es
#mq

启动类

@SpringBootApplication
@EnableDiscoveryClient//开启服务注册
@EnableFeignClients  //开启openfein客户端调用
public class CategoryApplication {}

@SpringBootApplication
@EnableDiscoveryClient
public class ProductApplication {}

CategoryController.java

//com.jun.entity
public class Product {
    private Integer id;
    private String name;
    private Double price;
    private Date bir;}
    
//com.jun.controller
@RestController
public class CategoryController {
    private static final Logger log = LoggerFactory.getLogger(CategoryController.class);
    @Autowired
    private ProductClient productClient;
    @GetMapping("/category")
    //public String category() {
    //public Product category() {
    //public List<Product> category() {
    //public Map<String, Object> category() {
    public String category() {
        log.info("category service……");
        //1.RestTemplate 2.RestTemplate+Ribbon() 3.OpenFeign
        //String result = productClient.test("小陈", 23); //【P29-10:30,这里可以变通】
        //String result = productClient.test1(21,"xiaoming"); //(P29-31:27)
        //String result = productClient.test2(new Product(1,"钢笔",24.3,new Date()));
        //String result = productClient.test3(new String[]{"21","22","23"});
        //String result = productClient.test4(new String[]{"21","23","24"});
        //return "category ok!! " + result;

        /*Product product = productClient.product(21);
        log.info("product: {}",product);
        return product;*/

        /*List<Product> products = productClient.findByCategoryId(1);
        products.forEach(product -> log.info("product: {}",product));
        return products;*/

        /**
         *在http://localhost:8787/category请求页面可以拿到数据(total:1000,rows:集合)。
         *尝试将页面中的数据打印出来。出错:java.util.LinkedHashMap cannot be cast to ......Product
         * 原因是:页面中的数据是JSON格式的字符串,P31-30:00
         */
        /*Map<String, Object> objectMap = productClient.findByCategoryIdAndPage(1, 5, 1);
        System.out.println(objectMap.get("total"));
        List<Product> rows = (List<Product>) (objectMap.get("rows"));
        rows.forEach(product -> System.out.println(product));
        return objectMap;*/

        /*String result = productClient.findByCategoryIdAndPage(1, 5, 1);
        System.out.println(result);
        //自定义json反序列化(json字符串转为对象),对象转为json叫序列化(JSONObject.toJSON)。
        JSONObject jsonObject = JSONObject.parseObject(result);
        System.out.println(jsonObject.get("total"));
        Object rows = jsonObject.get("rows");
        System.out.println(rows);
        //二次json反序列化
        List<Product> products = jsonObject.parseArray(rows.toString(), Product.class);
        products.forEach(product -> {
            log.info("product:{}",product);
        });
        return result;*/

        String result = productClient.product();
        //出现异常:Read timed out executing GET http://PRODUCT/product
        return result; //P31,openFeign默认超时处理
    }
}

ProductClient.java

//com.jun.feignclient
@FeignClient(value="PRODUCT")  //value:用来书写调用服务的id
public interface ProductClient {  //调用商品服务的接口
    //P31-30:00声明调用商品服务根据类别id查询分页查询商品信息以及总条数
    @GetMapping("/productList")
    //Map<String,Object> findByCategoryIdAndPage
    String findByCategoryIdAndPage(@RequestParam("page") Integer page,
                                   @RequestParam("rows") Integer rows,
                                   @RequestParam("categoryId") Integer categoryId);

    //P31-声明调用商品服务根据类别id查询一组商品信息
    @GetMapping("/products")
    List<Product> findByCategoryId(@RequestParam("categoryId") Integer categoryId);

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

    //P30-声明调用商品服务中的test4接口,传递一个list集合类型的参数
    //http://localhost:8788/test4?ids=21&ids=22&ids=23
    @GetMapping("/test4")
    String test4(@RequestParam("ids") String[] ids);

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

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

    //2.声明调用商品服务中test1接口:
    //路径传递参数,在openfeign接口声明中必须给参数加入注解:@PathVariable
    @GetMapping("/test1/{id}/{name}")
    String test1(@PathVariable("id") Integer id, @PathVariable("name") String name);

    //1.声明调用商品服务中的test接口传递name,age两个参数,test?name=xxx&age=23
    //queryString方式传递参数:在openfeign接口声明中必须给参数加入注解。@RequestParam
    @GetMapping("/test")
    String test(@RequestParam("name") String name, @RequestParam("age") Integer age);
    //调用商品服务
    @GetMapping("/product")
    String product();
    @GetMapping("/list")
    String list();
}

ProductController.java

//sc07_product:com.jun.vos
//定义用来接收集合类型参数的对象
public class CollectionVO {
    private List<String> ids;//接收集合声明在这里
    public List<String> getIds() {return ids;}
    public void setIds(List<String> ids) {this.ids = ids;}
}

//com.jun.controller
@RestController
public class ProductController {
    private static final Logger log = LoggerFactory.getLogger(ProductController.class);
    @Value("${server.port}")
    private int port;
    /**
     * 【P31-17:00】
     * 参数:page表示当前页,rows代表每页响应的记录数,(page-1)*rows代表起始条数
     * 1.根据类别id分页查询符合当前页集合数据,返回类型:List<Product>:
     *  select * from t_product where categoryId=? limt ?(page-1)*rows,?(rows)
     * 2.根据类别id查询当前类别下总条数totalCount,返回类型:int或Long类型,
     *  select count(id) from t_product where categoryId=?
     * 【返回值类型】:①可以自己定义一个对象接收:ProductDTO{List<Product>,Long totalCount}
     *  ②Map<String,Object>
     */
    @GetMapping("/productList")
    public Map<String,Object> findByCategoryIdAndPage(Integer page, Integer rows, Integer categoryId){
        log.info("当前页: {},每页显示记录数:{},当前类别id:{} ",page,rows,categoryId);
        Map<String, Object> map = new HashMap<>();
        List<Product> products = new ArrayList<>();
        products.add(new Product(1,"辣条",10.42,new Date()));
        products.add(new Product(2,"果冻",10.42,new Date()));
        products.add(new Product(3,"冰棍",10.42,new Date()));
        int total = 1000;
        map.put("rows",products);
        map.put("total", total);
        return map;
    }

    //P31-声明调用商品服务根据类别id查询一组商品信息
    @GetMapping("/products")
    public List<Product> findByCategoryId(Integer categoryId){
        log.info("类别id: {}",categoryId);
        //调用业务逻辑根据类别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;
    }

    //P31-定义一个接口接收id类型参数,返回一个基于id查询的对象
    @GetMapping("/product/{id}")
    public Product product(@PathVariable("id") Integer id){
        log.info("id:{}",id);
        return new Product(id,"彩笔",23.23,new Date());
    }

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

    //4.定义个接口接受数组类型参数
    @GetMapping("/test3")
    public String test3(String[] ids){
        for (String id : ids) {
            log.info("id: {}",id);
        }
        //手动转为list List<String> strings = Arrays.asList(ids); (P30-8:25)
        return "test3 ok,当前服务端口为: "+port;
    }

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

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

    //1.定义一个接受零散类型参数接口,queryString(?的方式)
    @GetMapping("/test")
    public  String test(String name, Integer age){
        log.info("name:{},age:{}",name,age);
        return "test ok,当前提供服务的端口为: "+port;
    }

    @GetMapping("/product")
    public String product() throws InterruptedException {
        log.info("进入商品服务.....");
        Thread.sleep(2000);  //P32,openFeign的默认超时处理
        //出现异常:Read timed out executing GET http://PRODUCT/product
        return  "product ok,当前提供服务的端口:"+port;
    }
    @GetMapping("/list")
    public String list(HttpServletRequest request, String color){
        String header = request.getHeader("User-Name");
        System.out.println("获取对应请求参数 color: "+color);
        System.out.println("获取请求头信息: "+header);
        log.info("商品列表服务");
        return "list ok当前提供服务端口: "+port;
    }
}

OpenFeign实现服务间通信响应处理

使用OpenFeign调用服务,并返回对象

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

使用OpenFeign调用服务,并返回集合

//P31-声明调用商品服务根据类别id查询一组商品信息
@GetMapping("/products")
List<Product> findByCategoryId(@RequestParam("categoryId") Integer categoryId);

使用OpenFeign调用服务,返回JSON格式的字符串

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

OpenFeign的默认超时处理

  • 默认的超时处理:使用OpenFeign组件在进行服务间通信时要求被调用服务必须在1s内给予响应,一旦服务执行业务逻辑时间超过1s,OpenFeign组件将直接报错:Read timed out executing GEThttp://PRODUCT/product
  • 修改OpenFeign的超时时间。P32-15:00