您现在的位置是:首页 >技术杂谈 >Spring Cloud OpenFeign源码解析,代理的魅力一展无遗网站首页技术杂谈

Spring Cloud OpenFeign源码解析,代理的魅力一展无遗

秃了也弱了。 2024-09-17 00:01:05
简介Spring Cloud OpenFeign源码解析,代理的魅力一展无遗

一、OpenFeign简介

关于OpenFeign的使用,请移步:
SpringCloud-OpenFeign官方文档使用大全详解

本文主要分析OpenFeign的源码。

二、OpenFeign中Bean的动态装载

关于SpringBoot的Enable模块设计,请参考:
Spring注解驱动原理及源码,深入理解Spring注解驱动

1、@EnableFeignClients

OpenFeign使用@EnableFeignClients 才会开启OpenFeign,我们从下面这个注解进行切入,这个注解开启了FeignClient的解析过程。

@EnableFeignClients("com.demo")

这个注解的声明如下,它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来去看一下FeignClientsRegistrar的定义。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	// 省略属性
}

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
	BeanDefinitionRegistry registry) {
		//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
		//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
		//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
}

registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册。

registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中。

2、registerFeignClients

registerFeignClients 方法,这个方法主要是扫描类路径下所有的@FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

	LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
	Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
	final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
	if (clients == null || clients.length == 0) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
		Set<String> basePackages = getBasePackages(metadata);
		for (String basePackage : basePackages) {
			candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
		}
	}
	else {
		for (Class<?> clazz : clients) {
			candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
		}
	}
	// 在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。
	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"));
			// 注册bean
			registerFeignClient(registry, annotationMetadata, attributes);
		}
	}
}


private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
		Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
	Class clazz = ClassUtils.resolveClassName(className, null);
	ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
			? (ConfigurableBeanFactory) registry : null;
	String contextId = getContextId(beanFactory, attributes);
	String name = getName(attributes);
	FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
	factoryBean.setBeanFactory(beanFactory);
	factoryBean.setName(name);
	factoryBean.setContextId(contextId);
	factoryBean.setType(clazz);
	factoryBean.setRefreshableClient(isClientRefreshEnabled());
	// BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类
	BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
		factoryBean.setUrl(getUrl(beanFactory, attributes));
		factoryBean.setPath(getPath(beanFactory, attributes));
		factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
		Object fallback = attributes.get("fallback");
		if (fallback != null) {
			factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
					: ClassUtils.resolveClassName(fallback.toString(), null));
		}
		Object fallbackFactory = attributes.get("fallbackFactory");
		if (fallbackFactory != null) {
			factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
					: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
		}
		// 获取虚拟客户端
		return factoryBean.getObject();
	});
	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
	definition.setLazyInit(true);
	validate(attributes);

	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
	beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
	beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

	// has a default, won't be null
	boolean primary = (Boolean) attributes.get("primary");

	beanDefinition.setPrimary(primary);

	String[] qualifiers = getQualifiers(attributes);
	if (ObjectUtils.isEmpty(qualifiers)) {
		qualifiers = new String[] { contextId + "FeignClient" };
	}

	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

	registerOptionsBeanDefinition(registry, contextId);
}

3、FeignClientFactoryBean.getObject

我们可以发现,FeignClient被动态注册成了一个FactoryBean,Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。

在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。

工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。

简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。

getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。

// org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject
@Override
public Object getObject() {
	return getTarget();
}
<T> T getTarget() {
	// FeignContext注册到容器是在FeignAutoConfiguration上完成的
	// 在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
	FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
			: applicationContext.getBean(FeignContext.class);
	// 构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
	Feign.Builder builder = feign(context);

	if (!StringUtils.hasText(url)) { // //如果url为空,则走负载均衡,生成有负载均衡功能的代理类

		if (LOG.isInfoEnabled()) {
			LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
		}
		if (!name.startsWith("http")) {
			url = "http://" + name;
		}
		else {
			url = name;
		}
		url += cleanPath();
		// 判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
		return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
	}
	//如果指定了url,则生成默认的代理类
	if (StringUtils.hasText(url) && !url.startsWith("http")) {
		url = "http://" + url;
	}
	String url = this.url + cleanPath();
	Client client = getOptional(context, Client.class);
	if (client != null) {
		if (client instanceof FeignBlockingLoadBalancerClient) {
			// not load balancing because we have a url,
			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
			client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
		}
		if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
			// not load balancing because we have a url,
			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
			client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
		}
		builder.client(client);
	}
	//生成默认代理类
	Targeter targeter = get(context, Targeter.class);
	// 最终调用
	return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

4、loadBalance

生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端。

// org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
	// 从IOC容器中获取一个Client,注意这里的Client是根据serviceId进行隔离的,每个ServiceId获取的Client是不同的
	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-loadbalancer?");
}

Client默认是LoadBalancerFeignClient,是在FeignRibbonClientAutoConfiguration中自动注册的:

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })

protected <T> T loadBalance(Builder builder, FeignContext context,
	HardCodedTarget<T> target) {
		Client client = (Client)this.getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = (Targeter)this.get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		} else {
			throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}
}

5、DefaultTarget.target

最后执行的target方法,调用DefaultTarget的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);
	}
}

build会返回一个ReflectiveFeign,携带者非常多的参数以及配置信息,最终调用了ReflectiveFeign的newInstance方法。

// feign.Feign.Builder#target(feign.Target<T>)
public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

public Feign build() {
  Client client = Capability.enrich(this.client, capabilities);
  Retryer retryer = Capability.enrich(this.retryer, capabilities);
  List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
      .map(ri -> Capability.enrich(ri, capabilities))
      .collect(Collectors.toList());
  Logger logger = Capability.enrich(this.logger, capabilities);
  Contract contract = Capability.enrich(this.contract, capabilities);
  Options options = Capability.enrich(this.options, capabilities);
  Encoder encoder = Capability.enrich(this.encoder, capabilities);
  Decoder decoder = Capability.enrich(this.decoder, capabilities);
  InvocationHandlerFactory invocationHandlerFactory =
      Capability.enrich(this.invocationHandlerFactory, capabilities);
  QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);

  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}

6、ReflectiveFeign.newInstance生成代理

这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。

// feign.ReflectiveFeign#newInstance
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
	//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
  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)));
    }
  }
  // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
  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;
}

创建的InvocationHandler 实际上默认是FeignInvocationHandler,也就是说,最终注入的FeignClient接口,实际上调用目标方法时会调用FeignInvocationHandler的invoke方法。

(1)targetToHandlersByName.apply(target)

根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。

// feign.ReflectiveFeign.ParseHandlersByName#apply
public Map<String, MethodHandler> apply(Target target) {
  List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
  Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
  for (MethodMetadata md : metadata) {
    BuildTemplateByResolvingArgs buildTemplate;
    if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
      buildTemplate =
          new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
    } else if (md.bodyIndex() != null || md.alwaysEncodeBody()) {
      buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
    } else {
      buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
    }
    if (md.isIgnored()) {
      result.put(md.configKey(), args -> {
        throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
      });
    } else {
      result.put(md.configKey(),
          factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
    }
  }
  return result;
}

7、小总结

通过上面的分析,我们知道,Feign定义的FeignClient接口,使用@Autowire自动注入时,最终会调用FactoryBean的getObject方法,而getObject方法最终通过jdk动态代理生成一个代理类,调用目标方法时会调用FeignInvocationHandler的invoke方法。

三、OpenFeign调用过程

以下是一个FeignClient的接口实例,当调用order方法时,就会执行FeignInvocationHandler的invoke方法。

@FeignClient(name = "order-service")
public interface OrderSurface {

    /**
     * 触发直播回放定时任务 task -live
     */
    @GetMapping(value = "/order")
    String order();
// feign.ReflectiveFeign.FeignInvocationHandler#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if ("equals".equals(method.getName())) {
    try {
      Object otherHandler =
          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }
  // 最终调用的是SynchronousMethodHandler的invoke方法
  return dispatch.get(method).invoke(args);
}

在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。

1、SynchronousMethodHandler.invoke

这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下:

// feign.SynchronousMethodHandler#invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
  // template包含url、method元数据等等所有数据
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  // 选项
  Options options = findOptions(argv);
  // 重试
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {
      return executeAndDecode(template, options);
    } 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;
    }
  }
}

在这里插入图片描述

2、executeAndDecode

经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。

// feign.SynchronousMethodHandler#executeAndDecode
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  //转化为Http请求报文
  Request request = targetRequest(template);

  if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
  }

  Response response;
  long start = System.nanoTime();
  try {
    //发起远程通信
    response = client.execute(request, options);
    // ensure the request is set. TODO: remove in Feign 12
    //获取返回结果
    response = response.toBuilder()
        .request(request)
        .requestTemplate(template)
        .build();
  } catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
      logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
    }
    throw errorExecuting(request, e);
  }
  long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


  if (decoder != null)
    return decoder.decode(response, metadata.returnType());

  CompletableFuture<Object> resultFuture = new CompletableFuture<>();
  asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
      metadata.returnType(),
      elapsedTime);

  try {
    if (!resultFuture.isDone())
      throw new IllegalStateException("Response handling not done");

    return resultFuture.join();
  } catch (CompletionException e) {
    Throwable cause = e.getCause();
    if (cause != null)
      throw cause;
    throw e;
  }
}

最终拼装http请求信息,将请求发送出去。

总结

大致上来说,Feign将接口进行了代理,通过代理类最终拼装http请求发送到指定的服务提供者,并且还有负载均衡的功能。

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。