> 文章列表 > feign技巧 - 同时支持基于url和服务名的调度

feign技巧 - 同时支持基于url和服务名的调度

feign技巧 - 同时支持基于url和服务名的调度

0. 目录

      • 1. 背景
      • 2. 实现
      • 3. 相关

1. 背景

本文尝试解决在Feigin使用过程中,希望定义的接口:

  1. 既支持基于服务名的负载均衡调度的请求调用;
  2. 又支持基于指定url地址的请求调用。

2. 实现

在前面的feign源码解析 - 初始化我们顺带介绍过可以通过"在方法参数上附加一个URI类型参数,来实现在运行时动态指定目标服务地址"。这种方式是存在一定缺陷的 —— 那就是你在定义方法所在的接口时,配置的@FeignClients必须对其url属性进行显式赋值。于是矛盾就出现了:

  1. 如果对@FeignClients的url属性进行了显式赋值,那我们在使用feign方法发起请求时,就会失去"基于服务名的负载均衡调度"能力。
  2. 如果不对@FeignClients的url属性进行了显式赋值,虽然获得了"基于服务名的负载均衡调度"能力,但之后通过feign接口发起请求调用时,默认feign会将你传入URI方法参数中的ip地址作为服务名去寻找对应的目标主机,而很明显其并不存在。

综上,实现思路也就是浮出水面了 —— 默认启用"基于服务名的负载均衡调度"能力,通过自定义扩展,在用户传入URI类型参数时,将发起请求的方式修改为直接基于传入的URI代表的地址。

样例代码如下:

//   配置// ============ 同时支持url和服务名// 只需要向容器中注入自定义的Client实现, 就算是完成了绝大部分的扩展操作。@Beanpublic Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory,okhttp3.OkHttpClient okHttpClient) {final OkHttpClient delegate = new OkHttpClient(okHttpClient);return new LoadBalancerFeignClientEx(delegate, cachingFactory, clientFactory);}// 注意这里的 implements Client 不能省略public static class LoadBalancerFeignClientEx extends LoadBalancerFeignClient implements Client {// 代表当前不指示feign发起请求时的url地址, 采用"服务名"的形式进行标准的负载均衡调用// BuildTemplateByResolvingArgs.create(...) 中会校验URI类型参数, 不允许为null, 于是我们采用 http://__NONE__这样一个固定值来内部约定当前是需要进行标准的负载均衡调度public static final String NONE_ULI_STR = "__NONE__";public static final URI NONE_URI = URLUtil.toURI("http://" + NONE_ULI_STR);private final Client delegate;public LoadBalancerFeignClientEx(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory,SpringClientFactory clientFactory) {super(delegate, lbClientFactory, clientFactory);this.delegate = delegate;}@Overridepublic Response execute(Request request, Options options) throws IOException {final URI asUri = URI.create(request.url());final String clientName = asUri.getHost();// Validator是hutool中的工具类if (Validator.isIpv4(clientName)) {// 直接调度return delegate.execute(request, options);} else {if (clientName.equals(NONE_ULI_STR)) {// 这里有个隐含的前提: hystrix的新建线程名采用的是默认的 hystrix-{servicename}-{num}final String currentThreadName = Thread.currentThread().getName();final String newUrl = request.url().replace(NONE_ULI_STR,StrUtil.split(currentThreadName, '-').get(1));ReflectUtil.setFieldValue(request, "url", newUrl);}// 基于服务名的负载均衡调度return super.execute(request, options);}}}// 应用
 定义feign接口
// 实现:
//	1. 方法中如果传递的 URI类型参数不为null, 则按照指定的url进行发送请求
//	2. 方法中如果传递的 URI类型参数为null,  则按照标准微服务名选举节点之后进行发送请求
// ========================================================
//	注意: 
//	1. @FeignClient url不要配置, 让 FeignClientFactoryBean.getTarget()方法中认为当前是LoadBalancer, 这样逻辑进入 LoadBalancerFeignClientEx 时救可以正常生效了
//	2. BuildTemplateByResolvingArgs.create(...) 中会校验URI参数, 不允许为null, 于是我们采用 http://__NONE__这样一个固定值来代表需要进行标准的负载均衡调度
@FeignClient(name = "projectB3"/*, url = "http://127.0.0.1:801"*/, fallbackFactory = FeignCallServiceFallbackFactory.class, configuration = FeignLoggerConfig.class)
public interface FeignDynamicHostCallService3 {/*** <p> 有时候,我们可能会需要动态更改请求地址的host,也就是@FeignClient中的url的值在我调用的是才确定。* <p> 在定义的接口的方法中,添加一个URI类型的参数即可,该值就是新的host。此时@FeignClient中的url值在该方法中将不再生效。* <p> 影响的是{@code MethodMetadata.urlIndex}字段* @param name* @param newHost* @return*/@RequestMapping(value = "/projectB/{name}", method = RequestMethod.GET)String callWithDynamicHost3(@PathVariable(value = "name") String name, URI newHost); 调用定义的feign接口@PostMapping("/dynamicHost/{name}")public String dynamicHost(@PathVariable String name) {// 抛出异常位置: BuildTemplateByResolvingArgs.create(Object[] argv)// 解析参数URI的位置: Contract.BaseContract.parseAndValidateMetadata(Class<?> targetType, Method method)System.out.println("PROJECT-B : " + feignDynamicHostCallService3.callWithDynamicHost3(name, LoadBalancerFeignClientEx.NONE_URI));System.out.println("PROJECT-B : "+ feignDynamicHostCallService3.callWithDynamicHost3(name, URLUtil.toURI("http://127.0.0.1:801")));return "hello";}	

3. 相关

  1. feign源码解析 - 初始化
  2. feign源码解析 - 运行时
  3. Feign扩展 - 进程内调用