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实现, 就算是完成了绝大部分的扩展操作。
	@Bean
	public 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;
		}

		@Override
		public 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 {
	/**
	 * 

有时候,我们可能会需要动态更改请求地址的host,也就是@FeignClient中的url的值在我调用的是才确定。 *

在定义的接口的方法中,添加一个URI类型的参数即可,该值就是新的host。此时@FeignClient中的url值在该方法中将不再生效。 *

影响的是{@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扩展 - 进程内调用

你可能感兴趣的:(SpringCloud,java,feign,springcloud)