本文采用nacos进行整合测试,具体源码见gitee链接:OpenFeign源码环境-develop
1.1 搭建生产者-订单服务 1.1.1 pom
org.springframework.cloud
spring-cloud-starter-openfeign
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-web
1.1.2 yaml
server:
port: 3333 #启动端口
spring:
application:
name: provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
1.1.3 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class NacosProviderApplication {
public static void main(String[] args)
{
SpringApplication.run(NacosProviderApplication.class, args);
}
}
1.1.4 业务类
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Value("${server.port}")
String port;
@GetMapping("/find/instance")
public String service() {
String applicationName = "provider";
String result = applicationName + "-" + port;
System.out.println("**********" + result);
return result;
}
}
1.2 搭建消费者-用户服务
1.2.1 pom
org.springframework.cloud
spring-cloud-starter-openfeign
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-web
1.2.2 yaml
server:
port: 5555 #启动端口
spring:
application:
name: consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
1.2.3 OpenFeign接口
// name: 微服务的名称 path: 服务生成者的调用前缀
@FeignClient(name = "provider", path = "/provider")
public interface ProviderClient {
@GetMapping("/find/instance")
String service();
}
1.2.4 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class NacosConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
}
1.2.5 业务类
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Value("${server.port}")
String port;
// 动态代理对象,内部远程调用服务生产者
@Autowired
ProviderClient providerClient;
@GetMapping("/find/instance")
public String service() {
// 远程调用
String providerResult = providerClient.service();
String result = "consumer invoke: " + port + " | " + providerResult;
System.out.println("****************" + result);
return result;
}
}
1.3 启动测试
1.3.1 启动Nacos
startup.cmd -m standalone
官方文档:
https:/Icloud.spring.iolspring-cloud-static/Greenwich.SR2/single/spring-cloud.html
日志级别:
1.NONE【性能最佳,适用于生产】︰不记录任何日志(默认值)。
2.BASIC【适用于生产环境追踪问题】︰仅记录请求方法、URL、响应状态代码以及执行时间。
3.HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
4.FULL【比较适用于开发及测试环境定位问题】︰记录请求和响应的header、body和元数据。
2.1.1 application.yaml配置# 设置Feign接口的日志级别 (前提)
logging:
level:
org.springframework.web: trace
com.best.nacos.client: debug
# 设置要调用微服务的日志级别:none, basic, headers, full
feign:
client:
config:
provider.loggerLevel: full
full日志内容

第1步:在Feign接口注解上面配置configuration
第2步:通过注册Bean来设置日志记录级别
// name: 微服务的名称 path: 服务生成者的调用前缀
@FeignClient(name = "provider", path = "/provider", configuration = FeignConfiguration.class)
public interface ProviderClient {
@GetMapping("/find/instance")
String service();
@RequestLine("GET /find/instance")
String service1();
}
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
// 设置日志级别: None,Basic,Headers,Full
return Logger.Level.FULL;
}
}
2.2 实现拦截器设置公共参数
2.2.1 编写拦截器,实现RequestInterceptor接口
public class MyInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
System.out.println("===============执行拦截器apply==============");
template.header("token", "jak");
}
}
2.2.2 加入拦截器配置
1.java配置
@Bean
public RequestInterceptor interceptor() {
return new MyInterceptor();
}
2.配置文件配置
feign:
client:
config:
# 配置拦截器
provider.requestInterceptors[0]: com.best.nacos.interceptor.MyInterceptor
3.消费端测试
4.生产端测试
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Value("${server.port}")
String port;
@GetMapping("/find/instance")
public String service(@RequestHeader("token") String token) {
String applicationName = "provider";
String result = applicationName + "-" + port;
System.out.println("**********" + result);
System.out.println("token: " + token);
return result;
}
}
RPC:(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
1. 在Feign底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类。
2.根据Contract协议规则,解析接口类的注解信息,解析成内部表现
Feign默认的协议规范
3.基于RequestBean,动态生成Request。
根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request对象
4.使用Encoder将Bean转换成Http报文正文(消息解析和转码逻辑),Feign最终会将请求转换成Http消息发送出去,传入的请求对象最终会解析成消息体。
目前Feign有以下实现
5.拦截器负责对请求和返回进行装饰处理
在请求转换的过程中,Feign抽象出来了拦截器接口,用于用户自定义对请求的操作
例如,希望Http消息传递过程中被压缩,可以定义一个请求拦截器
public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
super(properties);
}
@Override
public void apply(RequestTemplate template) {
// 在Header头部添加相应的数据信息
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
}
}
6.日志记录
在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息,并且将日志的输出定义了四个等级:
7.基于重试器发送HTTP请求
Feign内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心
重试器有如下几个控制参数:
@Override
public Object invoke(Object[] argv) throws Throwable {
// 根据输入参数,构造Http请求
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
// 克隆出一份重试器
Retryer retryer = this.retryer.clone();
// 尝试最大次数,如果中间有结果,直接返回
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
重试器有如下几个控制参数:
8.发送Http请求
Feign真正发送HTTP请求是委托给feign.Client来做的
Feign默认底层通过JDK的java.net.HttpURLConnection实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection链接,
这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient或者OkHttp3等基于连接池的高性能Http客户端。
3.2 Feign如何进行服务调用feign.ReflectiveFeign#newInstance: 获取代理对象
InvocationHandlerFactory: 控制反射方法调用
Client:实现类LoadBalancerFeignClient和Default
Feign在封装了相关的请求参数RequestTemplate后,发起服务远程请求调用
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 拼接完整的request请求
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 发起请求,并获取返回结果Response
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
// 解析Response请求,获取最后的结果
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
// 响应返回结果
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
从源码中可以很直观的看到真正发起服务请求调用的是feign.Client#execute
LoadBalancerFeignClient为 Ribbon负载均衡客户端实现类
1、LoadBalancerFeignClient#execute()是整个Feign Client的执行入口
2、LoadBalancerFeignClient#execute()方法主线逻辑:
- 封装 RibbonReuqest请求对象
- 从容器中获取 Ribbon请求配置参数信息IConfigClient
- 根据RibbonRequest和IConfigClient构造 FeignLoadBalancer。
3、FeignLoadBalancer根据Ribbon的IRule规则从lloadbalancer获取一个服务实例server。
四、Feign源码解析processOn参考、流程图
视频教程