OpenFeign
一、基本使用
1、引入依赖
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId><groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
对于loadbalancer做一个解释,开始用的是Ribbon,2020年之后开始使用loadbalancer代替它,两者最主要的区别是,Ribbon是一个较为底层的负载均衡器,需要开发人员手动配置均衡策略和服务发现机制,但是loadbalancer提供了更高层次的抽象,将负载均衡策略和服务发现机制实现了封装,更加容易使用。
2、启动类上添加@EnableFeignClients注解,表示开启OpenFeign
3、编写接口,看代码:
@FeignClient(value = "item-service")
public interface ItemClient {@GetMapping("item/{id}")ResponseEntity<Item> queryById(@PathVariable("id") Integer id);
}
@FeignClient括号里面value对应的值是被请求的服务的名称,接口中的方法就跟在controller里面写的一样,只是不需要实现,需要提供请求方式和路径,例如:@GetMapping("item/{id}"),还需要提供返回值,请求参数,请求参数的注解,都不能少,例如:ResponseEntity<Item> queryById(@PathVariable("id") Integer id),需要将返回值的类从其他服务复制一份过来。
二、原理
1、OpenFeign调用接口没有具体实现,是因为使用了代理,代理类是FeignInvocationHandler实现了InvocationHandler
static class FeignInvocationHandler implements InvocationHandler
2、FeignInvocationHandler类的主要方法是invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
下面是方法的一部分:
if (!"equals".equals(method.getName())) {if ("hashCode".equals(method.getName())) {return this.hashCode();} else {return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);}} else {try {Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return this.equals(otherHandler);} catch (IllegalArgumentException var5) {return false;}}}
除了基本的hashCode这样的方法之外,都要走代理invoke方法。代理的过程:
(1)首先根据接口定义的信息创建一个RequestTemplate对象,这个对象从接口获取到请求方法,请求路径,请求参数,服务名称,模板创建完成之后是这样:GET /item/2 HTTP/1.1
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
(2)根据模板创建一个请求对象Request,此时请求的路径基本成型,只是还有获取到服务的ip和端口,暂时用服务名代替,例如:GET http://item-service/item/2,通过Request对象可以获取到被请求服务的服务列表
Request request = this.targetRequest(template);
(3)通过loadBalancerClient.choose负载均衡,从实例列表中选出一个实例,获取该实例的ip和端口
ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);
ServiceInstance是一个实例,大概信息是:
最后拼接成url:
(4)使用client 类型的delegate发送http请求,但是这里的发送可以优化,使用连接池技术,避免每次创建和销毁链接导致的系统性能开销,推荐使用OKHttp。
优化:
添加依赖
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
配置applicaton.yml
feign:okhttp:enabled: true
避坑:
遇到过两个错误
(1)feign/Request$ProtocolVersion
(2)feign.Request$HttpMethod.isWithBody()Z
原因是因为OKHttp依赖的版本和cloud不匹配导致的,我这里用的是这一组,可以参考下:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>3.1.0</version> </dependency> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId><version>3.1.0</version> </dependency> <dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId><version>10.12</version> </dependency>
springboot的版本是:
<version>2.6.13</version>
三、最佳实践
如果说大量使用到服务之间的通信调用,那么可以将Client单独抽离到一个模块,实现统一维护。
四、OpenFeign日志开启
1、首先是client所在的包的日志级别必须是debug,yml文件的配置如下,包名自己换:
logging:level:com.cart.cartservice: debugpattern:dateformat: HH:mm:ss:SSS
2、编写配置类
public class DefaultFeignConfig {@Beanpublic Logger.Level feignLoggerLevel(){return Logger.Level.BASIC;}
}
Logger.Level.BASIC是日志级别:
NONE:不记录,这是默认值
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。
HEADERS:在BASIC的基础上额外的记录了请求和响应的头
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
3、在启动类的注解上添加扫描的包和配置类
@EnableFeignClients(basePackages = "com.cart.cartservice",defaultConfiguration = DefaultFeignConfig.class)