前言
SpringCloud之 OpenFeign服务接口学习!
文章目录
一、Feign与OpenFeign概念
-
Feign
是一个声明式的伪Http客户端(因为底层依然使用的是RestTemplate来实现,基于Http应用层协议实现),它的使用方法是通过在一个服务接口上面增加注解(可以使用SpringMVC的注解)。Feign也支持可插拔式的编码器和解码器。Spring Cloud对Feign进行了封装,使它支持了Spring MVC标准注解和 HttpMessageConverts,Feign可以与Ribbon和Eureka组合使用来支持负载均衡,Feign内部集成了Ribbon组件,可以实现请求的负载均衡。 -
前面在使用
Ribbon
+RestTemplate
时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是实际操作时对于服务依赖的调用可能不只一处,往往会一个接口被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装。因此Feign
在此基础上做了进一步的封装处理,由它来帮助我们实现依赖服务接口的定义,这样就能够实现服务端接口对应Feign
接口,客户端只用调用Feign
接口即可,Feign
的实现只需要接口+注解就可以实现。 -
Feign
还集成了Ribbon,并通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口并且以声明式的方法,来优雅而且简单的实现服务调用。 -
RestTemplate
与OpenFeign
的区别RestTemplate
的缺点包括:路径写死、不能自动转换响应结果为对应对象、必须集成Ribbon来实现负载均衡;OpenFeign
则以十分便捷的方式实现了服务间的通信,同时内部集成了Ribbon,具备负载均衡的特性。 -
Feign
与OpenFeign
的区别
二、OpenFeign的使用
OpenFeign是集成在客户端,为客户请求微服务而生效的,同时由于它还支持SpringMVC注解。因此我们可以通过SpringMVC注解的方式结合微服务ID、微服务ip地址创建接口,这样商品服务端就只需要调用本地的接口即可实现目的。
-
pom.xml
关键需要引入spring-cloud-starter-openfeign
依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- application.yml
server:
port: 80
eureka:
instance:
prefer-ip-address: true
instance-id: consumer-order-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureaka7002.com:7002/eureka
-
ConsumerController
模拟客户端请求服务端,模拟了无参数、简单参数、复杂参数、对象作为参数
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
PaymentClient paymentClient;
/**
* 用户查询单个订单信息
* @param id
* @return
*/
@GetMapping("/getById/{id}")
public ResultObj<Payment> getPayment(@PathVariable("id") Long id){
// return restTemplate.getForObject(PAYMENT_URL+"/payment/getById/"+id, ResultObj.class);
return paymentClient.getPaymentById(id);
}
@GetMapping("/all")
public ResultObj<List<Payment>> getPaymentList(){
return paymentClient.getAllPayment();
}
@GetMapping("/limit")
public ResultObj<List<Payment>> getLimit(@RequestParam("limit") int limit){
return paymentClient.getByLimit(limit);
}
@PostMapping("/create")
public ResultObj createPayment(@RequestBody Payment payment){
// int a= 2;
// return paymentClient.createPayment(payment);
Payment payment1 = new Payment(36L,"qqqqq");
System.out.println("客户端:"+payment1);
ResultObj resultObj = paymentClient.insertPayment(payment1);
return resultObj;
}
}
-
PaymentClient
定义客户端相应的OpenFeign
接口,客户端Controller
通过调用这个接口的方法来实现访问目标微服务。
/**
* 根据OpenFeign编写的客户端接口
*/
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentClient {
/**
* 根据ID获取指定服务
* 注意参数作为路径时必须加上@PathVariable
* @return
*/
@GetMapping("/payment/getById/{id}")
public ResultObj<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping("/payment/getAll")
public ResultObj<List<Payment>> getAllPayment();
/**
* 方法对应多个普通参数
* 注意参数作为路径时必须加上@RequestParam
* @return
*/
@GetMapping("/payment/queryAllByLimit")
public ResultObj<List<Payment>> getByLimit(@RequestParam("limit") int limit);
/**
* 传递对象作为参数
* 注意参数作为路径时必须加上@RequestBody
* @return
*/
@PostMapping("/payment/create")
public ResultObj createPayment(@RequestBody Payment payment);
@PostMapping(value = "/payment/insert")
public ResultObj insertPayment(@RequestBody Payment payment);
}
-
PaymentController
微服务端对应Controller
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/getById/{id}")
public ResultObj<Payment> selectOne(@PathVariable("id") Long id){
return new ResultObj<Payment>("200","serverPort:"+serverPort,paymentService.queryById(id));
}
@GetMapping("/getAll")
public ResultObj<List<Payment>> selectAll(){
ResultObj<List<Payment>> ans = ResultObj.success(paymentService.queryAll());
return ans;
}
@GetMapping("/queryAllByLimit")
public ResultObj<List<Payment>> queryAll(@RequestParam(defaultValue = "0") int offset,
@RequestParam(defaultValue = "5") int limit){
return ResultObj.success(paymentService.queryAllByLimit(offset,limit));
}
@PostMapping("/insert")
public ResultObj insertObj(@RequestBody Payment payment){
System.out.println(payment);
return ResultObj.success();
}
@PostMapping("/create")
public ResultObj addData(@RequestBody Payment payment){
System.out.println("success received Payment........");
Payment ans = paymentService.insert(payment);
if(ans != null){
return ResultObj.success();
}
else
return ResultObj.failure(Common.SERVER_ERROR);
}
}
注意OpenFeign
参数传递之数组和集合类型参数,在编写接口时,数组需要注上@RequestParam("数组名称")
如果有多个参数需要返回,一般先返回客户端json字符串,然后利用jackson
工具来解析对应的json字符串从中获取数据。
微服务提供端
@GetMapping("/testManyArgs")
public ResultObj testManyArgs(){
String res = null;
Map<String,Object> ans = new HashMap<>();
try {
List<Payment> list = new ArrayList<>();
list.add(new Payment(1L,"阿萨德"));
list.add(new Payment(2L,"自行车"));
list.add(new Payment(3L,"徐徐线程"));
list.add(new Payment(4L,"水电费"));
ans.put("list",list);
ans.put("guigui","xxxx");
ObjectMapper mapper = new ObjectMapper();
res = mapper.writeValueAsString(ans);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return ResultObj.success(res);
}
消费者处理端
@GetMapping("/testManyArgs")
public ResultObj testArgs(){
ObjectMapper mapper = new ObjectMapper();
ResultObj res = paymentClient.testManyArgs();
String json = (String) res.getObject();
try {
Map<String,Object> tempMap = mapper.readValue(json, Map.class);
String str = (String) tempMap.get("guigui");
System.out.println(str);
String objstr = mapper.writeValueAsString(tempMap.get("list"));
List<Payment> paymentList = mapper.readValue(objstr, new TypeReference<List<Payment>>() {
});
for (Payment payment : paymentList) {
System.out.println(payment);
}
} catch (Exception e) {
e.printStackTrace();
}
return ResultObj.success();
}
测试
三、OpenFeign的超时处理
OpenFeign
默认调用超时服务,使用OpenFeign
组件在进行服务间通信时要求被调用服务必须在1s内给予响应,一旦服务执行业务逻辑时间超过1s,OpenFeign组件将直接报错。
修改OpenFeign
超时时间:
# 修改某个具体服务的超时时间
feign.client.config.服务ID.connectTimeout=5000 #配置指定服务连接超时
feign.client.config.服务ID.readTimeout=5000 #配置指定服务等待超时
# 修改所有默认服务的超时时间
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
四、OpenFeign日志展示
4.1 日志开启
默认OpenFeign
是没有开启日志记录的,因此需要在配置文件中开启对于日志记录的支持,OpenFeign
能够对于每个微服务申请一个日志记录功能。
-
开启日志展示
指定feign客户端所在包的日志级别:loggin.level.xxxx.xxx=debug
开启指定服务日志展示:feign.client.config.PRODUCTS.loggerLevel=full
开启全局服务的日志展示:feign.client.config.default.loggerLevel=full
-
feign的日志记录级别
1)NONE
:不记录任何日志;
2)BASIC
:仅仅记录请求方法,url,响应状态码及执行时间;
3)HEADERS
:记录Basic级别的基础上,记录请求和响应的Header;
4)FULL
:记录请求和响应的Header,body和元数据。