Feign 执行过程源码分析

例行分享大佬的优秀文章:
Feign分析
Feign详解

Feign源码

1.Feign的AutoConfiguration

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
……
}

@Configuration
public class FeignClientsConfiguration {
……
}

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

2.使用Feign注解:@EnableFeignClients

@EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {……}

这个注解主要为引入FeignClientsRegistrar,通过registerBeanDefinitions() 向容器中注入一些Bean。FeignClientsRegistrar的初始化过程下篇介绍:spring源码中常见‘父基类’分析
我们现在看下registerBeanDefinitions()方法中都做了写什么:

@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
  1. registerDefaultConfiguration:
    这个方法很好理解,Feign肯定会有配置信息的,肯定可以自定义,也有默认配置。源码就不贴了,这个方法的作用就是向spring容器中注入,EnableFeignClients这个注解中的defaultConfiguration 配置。
  2. registerFeignClients
    根据方法名推测就是注册FeignClients。我们也了解在Feign使用中我们可以同时使用多个@FeignClient

3.使用注解:@FeignClient

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

	
	@AliasFor("name")
	String value() default "";

	@Deprecated
	String serviceId() default "";

	String contextId() default "";

	@AliasFor("value")
	String name() default "";

	String qualifier() default "";

	String url() default "";

	boolean decode404() default false;

	Class<?>[] configuration() default {};

	Class<?> fallback() default void.class;

	Class<?> fallbackFactory() default void.class;

	String path() default "";

	boolean primary() default true;
}

我们可以通过这些属性定义独特唯一的FeignClient。
此时我们再接着看方法:
registerFeignClients

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));

					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

扫描找到所有的@FeignClient,并进行注册。注册时会将@FeignClient中的属性进行填充。我们可能已经注意到了,registerFeignClient()方法中注册@FeignClient时并没有直接使用相关的类,而是借助了这个FeignClientFactoryBean和BeanDefinition。这两个类的分析我们也放到了下篇文章:spring源码中常见‘父基类’分析
这里FeignClientFactoryBean的作用就是通过getObject()可以得到最终对象。
接下来我们分析下这个方法中都做了什么:

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = 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 = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}
  1. Feign.Builder builder = feign(context);
    builder中会set了encoder、decoder、contract这些感觉都是和http请求有关。然后呢又加入了configure。这个configure配置可以结合我们上边说的注解中的configure属性配置来理解这些配置的来龙去脉。
  2. 然后就是对url相关的处理。同时拿到容器中的httpClient,也是和发送 请求相关。
  3. 通过Feign的自动配置可知,Targeter有两种实现:
@Configuration
	@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
	protected static class HystrixFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new HystrixTargeter();
		}

	}

	@Configuration
	@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
	protected static class DefaultFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new DefaultTargeter();
		}

	}

对应的target方法为:

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		SetterFactory setterFactory = getOptional(factory.getName(), context,
				SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(factory.getName(), context, target, builder,
					fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder,
					fallbackFactory);
		}

		return feign.target(target);
	}
}

最终都会执行:feign.target(target)

  public <T> T target(Target<T> target) {
      return build().newInstance(target);
  }
  
 public Feign build() {
     SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
         new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
             logLevel, decode404, closeAfterDecode, propagationPolicy);
     ParseHandlersByName handlersByName =
         new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
             errorDecoder, synchronousMethodHandlerFactory);
     return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
  }

 public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}  
这个过程我们可以了解到,FeignClientFactoryBean的get Object()方法返回了proxy的动态代理。

方法最终执行会调用SynchronousMethodHandler.invoke()方法:

  • 先根据请求参数构建请求模板 RequestTemplate,就是处理 URI 模板、参数,比如替换掉 uri 中的占位符、拼接参数等。
  • 然后调用了 executeAndDecode 执行请求,并将相应结果解码返回。
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } 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;
      }
    }
  }
  1. 咱们反过来再接着看getObject()这个方法。
	if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

如果 为servicename的形式,那么则执行服务端负载均衡。继续看代码 executeAndDecode:

 Object executeAndDecode(RequestTemplate template) throws Throwable {
……
 response = client.execute(request, options);

这里的client有两个实现类:
在这里插入图片描述
这个client的注入的巧妙逻辑就在:

	Client client = getOptional(context, Client.class);
	
 public static class Builder {
	……
    private Client client = new Client.Default(null, null);
	……
}

我们看下LoadBalancerFeignClient.excute()方法:

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}
  • lbClient(clientName) 这个方法中的逻辑是和我们看Ribbon的源码是一致的
  • executeWithLoadBalancer 这个方法的涉及到rxjava回调的方式,所以肯定有一个地方产生对应的Server。

到此Feign源码部分就已经结束了。

Feign是用来干什么的

Feign是一种负载均衡的HTTP客户端, 使用Feign调用API就像调用本地方法一样,从避免了调用目标微服务时,需要不断的解析/封装json 数据的繁琐。Feign集成了Ribbon。Fegin是一个声明似的web服务客户端,它让微服务之间的调用变得更简单了,就类似controller调用service。

Q&A

Q1.为什么可以再FeignClient中直接使用springmvc相关的注解

	1.是Feign.Builder中的contract属性。spring容器中注入的bean为SpringMvcContract 。这个bean可以为我们处理FeignClient方法中一些特定的springmvc注解。

Q2.为什么Feign要求单一继承

代码层面:
  public <T> T newInstance(Target<T> target) {
	Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  ……
}
targetToHandlersByName.apply中parseAndValidatateMetadata:
代码中contact会对@FeignClient注解类进行validate 检查。

Q3. 为什么Feign中不能使用@GetMapping等注解

 同样是Feign.Builder中的contact属性会对target中的方法进行匹配处理。
 代码仍然是在newInstance()方法中

Q4.Feign方法中使用不带注解的自定义类

  contact会将这个参数加入到request body中。可能会引起请求方法的变化。

Q5.Feign如何解析@FeignClient中url

	如果@FeignClient没有配置url时,在生成feign的代理类时会将@FeignClient的name 加入到url,并且通过ribbon进行解析
//FeignClientFactoryBean.getObject()方法:
if (!StringUtils.hasText(this.url)) {
	String url;
	if (!this.name.startsWith("http")) {
		url = "http://" + this.name;
	}
	else {
		url = this.name;
	}
	url += cleanPath();
	return loadBalance(builder, context, new HardCodedTarget<>(this.type,
			this.name, url));
}

@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
						  SpringClientFactory clientFactory) {
	return new LoadBalancerFeignClient(new Client.Default(null, null),
			cachingFactory, clientFactory);
}

此时可以理解@FeignClient的name属性另一个作用,为被调用服务的注册名称。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值