OpenFeign与Feign的区别→
GitHub开源链接→
官网解释→
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口, 然后在上面添加注解@FeignClient。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters , Feign可以与Eureka和Ribbon组合使用以支持负载均衡.
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了服务提供方实例列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现服务的调用.
此处省略了服务注册与发现集群的创建过程, 本文主要讲OpenFeign的使用 ,参考请移步博客→搭建Eureka-Server集群
创建微服务父工程创建微服务服务工程[atguigu-cloud-2020], 来进行依赖的版本管理. 依赖如下:
创建服务提供者4.0.0 com.atguigu.springcloud atguigu-cloud-2020 1.0-SNAPSHOT cloud-api-commons cloud-consumer-order cloud-eureka-server-7001 cloud-eureka-server-7002 cloud-provider-payment-8001 cloud-provider-payment-8002 pom UTF-8 1.8 1.8 4.12 1.2.17 1.16.18 5.1.47 1.1.16 1.3.0 org.springframework.boot spring-boot-dependencies 2.2.2.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR1 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.1.0.RELEASE pom import mysql mysql-connector-java ${mysql.version} com.alibaba druid ${druid.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.spring.boot.version} junit junit ${junit.version} log4j log4j ${log4j.version} org.projectlombok lombok ${lombok.version} true org.springframework.boot spring-boot-maven-plugin true true
新增服务提供者模块[cloud-provider-payment-8001], [cloud-provider-payment-8002]
依赖编写配置atguigu-cloud-2020 com.atguigu.springcloud 1.0-SNAPSHOT 4.0.0 cloud-provider-payment-8001 org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.atguigu.springcloud cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba druid-spring-boot-starter 1.1.10 mysql mysql-connector-java org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
application.yml配置内容:
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
validation-query: select 1 from dual
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包
# 日志打印dao层的sql
logging:
level:
com.atguigu.springcloud.dao: debug
# eureka客户端配置
eureka:
client:
#表示是否将自己注册进EurekaServer,默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的服务注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://www.eureka01.com:7001/eureka,http://www.eureka02.com:7002/eureka
instance:
instance-id: cloud-provider-payment-8001 # 另一个是cloud-provider-payment-8002
prefer-ip-address: true
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mapper映射文件, mapper/PaymentMapper.xml
编写dao层INSERT INTO payment(SERIAL) VALUES(#{serial}); SELECT * FROM payment WHERe id=#{id};
com.atguigu.springcloud.dao.PaymentDao
package com.atguigu.springcloud.dao;
import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface PaymentDao {
int create(Payment payment);
Payment getPaymentById(@Param("id") Long id);
List findAll();
}
编写service层
com.atguigu.springcloud.service.impl.PaymentServiceImpl
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.service.PaymentService;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
@Override
public int create(Payment payment) {
return paymentDao.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
@Override
public List findAll() {
return paymentDao.findAll();
}
}
编写controller层
com.atguigu.springcloud.controller.PaymentController
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
@RequestMapping(value = "/payment")
public class PaymentController {
@Value(value = "${server.port}")
private String serverPort;
@Resource
private PaymentService paymentService;
@PostMapping(value = "/create")
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info(">>>插入结果: {}", result);
if (result > 0) {
return new CommonResult(200, "插入数据库成功, 服务端口: " + serverPort, payment);
} else {
return new CommonResult(444, "插入数据库失败", null);
}
}
@GetMapping(value = "/get/{id}")
public CommonResult get(@PathVariable(value = "id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info(">>>查询结果: {}", payment);
if (payment != null) {
return new CommonResult(200, "查询成功, 服务端口: " + serverPort, payment);
} else {
return new CommonResult(444, "没有对应记录,查询ID: " + id, null);
}
}
@GetMapping
public CommonResult findAll() {
List all = paymentService.findAll();
log.info(">>>查询结果: {}", all);
if (all != null) {
return new CommonResult(200, "查询成功, 服务端口: " + serverPort, all);
} else {
return new CommonResult(444, "没有记录", null);
}
}
@GetMapping(value = "/lb")
public String getPaymentLB() {
return serverPort;
}
@GetMapping(value = "/feign/timeout")
public String paymentFeignTimeOut() {
System.out.println("*****paymentFeignTimeOut from port: " + serverPort);
//暂停几秒线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
}
编写启动类
com.atguigu.springcloud.PaymentApplication8001
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentApplication8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication8001.class);
}
}
创建服务消费者
新增服务消费者模块[cloud-consumer-order]
依赖编写配置atguigu-cloud-2020 com.atguigu.springcloud 1.0-SNAPSHOT 4.0.0 cloud-consumer-order org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.atguigu.springcloud cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
application.yml配置内容:
server:
port: 80
spring:
application:
name: cloud-consumer-order
eureka:
client:
#表示是否将自己注册进EurekaServer,默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://www.eureka01.com:7001/eureka,http://www.eureka02.com:7002/eureka
instance:
instance-id: cloud-consumer-order-80
prefer-ip-address: true
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
#ribbon:
# #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间, 单位ms
# ConnectTimeout: 5000
# #指的是建立连接后从服务器读取到可用资源所用的时间,单位ms
# ReadTimeout: 5000
编写Feign接口
注解@FeignClient + 微服务调用接口方式使用Feign.
com.atguigu.springcloud.service.PaymentFeignService
package com.atguigu.springcloud.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Component
@FeignClient(name = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentFeignService {
@PostMapping(value = "/payment/create")
int create(Payment payment);
@GetMapping(value = "/payment/get/{id}")
CommonResult getPaymentById(@PathVariable(value = "id") Long id);
@GetMapping
CommonResult> findAll();
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeOut();
}
编写controller层
因为是服务消费方进行远程调用, 所以就写一个controller即可. open-feign声明式接口调用, 实现调用代码和业务逻辑的解耦.
com.atguigu.springcloud.controller.OrderController
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.lb.LoadBalancer;
import com.atguigu.springcloud.service.PaymentFeignService;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
@RestController
@RequestMapping(value = "/consumer")
public class OrderController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/feign/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeOut() {
return paymentFeignService.paymentFeignTimeOut();
}
}
编写配置类
com.atguigu.springcloud.config.ApplicationContextConfig
package com.atguigu.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
编写启动类
com.atguigu.springcloud.ConsumerApplication, 启动类上添加注解@EnableFeignClients开启Feign的配置功能.
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
//@RibbonClient(name = "CLOUD-PROVIDER-PAYMENT", configuration = {MySelfRule.class}) // 自定义负载均衡策略
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
测试OpenFeign调用
依次启动各个微服务应用, 访问 http://localhost/consumer/feign/payment/get/1, 调用成功.
从上面的测试结果来看, OpenFeign自带负载均衡功能, 因为OpenFeign内置了Ribbon.
超时设置,服务提供方8001故意写线程等待程序, 设置超时演示出错情况. com.atguigu.springcloud.controller.PaymentController#paymentFeignTimeOut
@Value(value = "${server.port}")
private String serverPort;
@GetMapping(value = "/feign/timeout") // /payment/feign/timeout
public String paymentFeignTimeOut() {
System.out.println("*****paymentFeignTimeOut from port: " + serverPort);
//暂停3秒钟线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
服务消费方80的FeignClient接口类添加对应的超时方法.
com.atguigu.springcloud.service.PaymentFeignService#paymentFeignTimeOut
@Component
@FeignClient(name = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentFeignService {
@PostMapping(value = "/payment/create")
int create(Payment payment);
@GetMapping(value = "/payment/get/{id}")
CommonResult getPaymentById(@PathVariable(value = "id") Long id);
@GetMapping
CommonResult> findAll();
// 新增的超时方法
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeOut();
}
服务消费方80的controller层添加超时方法.
com.atguigu.springcloud.controller.OrderController#paymentFeignTimeOut
@GetMapping(value = "/payment/feign/timeout") // /consumer/payment/feign/timeout
public String paymentFeignTimeOut() {
return paymentFeignService.paymentFeignTimeOut();
}
启动各个微服务应用, 访问 http://localhost/consumer/payment/feign/timeout, 出现报错:
Feign客户端默认只等待一秒钟,但是服务提供方处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
解决方案: 在配置文件中添加Feign客户端与服务提供方的连接和请求资源的超时时间设置.
#设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间, 单位ms ConnectTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间,单位ms ReadTimeout: 5000
再次测试, 访问 http://localhost/consumer/payment/feign/timeout, 等待几秒后正常返回.
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。
说白了就是对Feign接口的调用情况进行监控和输出.
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
编写Feign日志配置类 com.atguigu.springcloud.config.FeignConfig
package com.atguigu.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
为FeignClient接口指定日志输出配置
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug
测试结果
后台日志查看.
个人博客(1) Feign和OpenFeign两者区别
(2) 使用: 微服务调用接口 + 注解@FeignClient
(3) OpenFeign内置Ribbon, 自带负载均衡配置.
欢迎访问个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/



