上节说到ribbon+resttemplate不灵活,而且有一点没说,ribbon和eureka一样,2.X耐非也不维护了,只有1.X会继续活跃。所以就引出接下来要使用的openFeign了。
Feign是springcloud-spring公司的东西,贴下概念:
在代码里声明也比较简单,在做注册中心时写代码需要三步,分别是:引依赖、写配置、加注解;加入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;
}
}
可以看见商品服务最上面三个接口有简单的业务类代码了,返回类型也各不相同。
超时处理和日志使用
默认是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