Spring Cloud 之 OpenFeign 的使用
在spring cloud 体系中我们一般使用openfeign来实现服务间的访问,比如用户服务访问日志服务记录用户的一些操作。
我们选择openfeign的需要原因是openfeign作为spring cloud的亲儿子可以和spring cloud 完美集成,然后openfeign通过简单的注解就可以实现接口的定义和访问。
比如如下的一个接口定义
@FeignClient("service-auth/api") public interface AddressServiceClient { @GetMapping(value = "/address/findByIds") ListfindByIds(@RequestParam("ids") String[] ids); }
@FeignClient注解声明这个接口是一个REST客户端,里面的方法定义和@RestController中定义接口的方式一致
@FeignClient中有好几个属性可以定义,其实默认的name属性是必填的,表示Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现,另外还有一个url属性,启用这个属性后,name属性还是必须要填写但已无用,url的值表示为具体的服务地址,可能是一个ip地址也可能是一个域名。
比如@FeignClient(name="baidu",url="www.baidu.com")
.在内部处理逻辑中,url和name的值如果不是http开头的,会自动加上http,如果是https的则需要主动加上去了。
具体的源码在FeignClientFactoryBean.class
中
T getTarget() { FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class); Builder builder = this.feign(context); //this.url 指的是url属性的值 if (!StringUtils.hasText(this.url)) { // url的值将会变成name属性的值 if (!this.name.startsWith("http")) { // 判断url是不是http开头的 不是的话 补上协议头 this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); // 如果url是空的 则通过name获取一个负载均衡后的客户端实例 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); } else { if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + this.cleanPath(); Client client = (Client)this.getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = (Targeter)this.get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url)); } }
另外比较有用的属性比如@decode404,表示遇到了404问题是直接抛出FeignException还是调用feign的decode去处理异常。@path用于定义所有方法映射的统一前缀。
另外使用openfeign,需要在入口函数所在类加上@EnableFeignClients注解启用openfeign.
这个注解开启后,会自动扫描使用了@FeignClient的接口。
在实际使用的时候,可能会遇到一个问题,访问其他服务的时候,目标服务会返回401错误,调试的时候你会发现,openfeign调用的时候,request中并不含浏览器给出的认证信息,也就是request中的header信息丢了。
此时,你需要重新构造一个RequestInterceptor接口的实现。这是openfeign中给出的request的拦截器接口。接口定义了一个 void apply(RequestTemplate template);
.
我们需要对这个template执行一些操作,让他带上原本的header。
@Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { // 从spring bean容器中拿到当前请求的request HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); // 给requestTemplate重新赋值 requestTemplate.header("Authorization", request.getHeader("Authorization")); requestTemplate.header("Cookie", request.getHeader("Cookie")); }; }
最后,说一下feign的实现原理,简单的将就是java动态代理的应用。我们都知道接口是不能实例化的,声明的接口在运行时会被动态代理。我们常用的maybatis也是如此。
被代理的每一个方法都指代了一个访问地址,这些方法都会被转化为具体的RequestTemplate实现,默认会使用httpclient实现具体的请求,当然你也可以引入feign-okhttp这个包实现底层通过okhttp访问。
最后,如果开启了负债均衡,则还会通过负载均衡选取目标服务。