springcloud-openFeign简单梳理

OpenFeign

  openFeign是springcloud中,服务间进行调用的常用方式。了解它,可以更好的处理服务间调用问题。

@EnableFeignClients

springcloud-openFeign简单梳理_第1张图片
  每个@FeignClient注解标注的类,封装为BeanDefinition,FeignClientFactoryBean为BeanDefinition的InstanceSupplier属性。这是一个Supplier函数接口。再bean生命周期中实例化bean之前调用。如果bean对应的BeanDefinition中有这个属性,调用这个属性的get方法获取当前bean实例。

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

  项目模块装配Feign相关。
重点关注。@Import。导入要给FeignClientsRegistrar类。

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

  FeignClientsRegistrar类中实现接口ImportBeanDefinitionRegistrar,通过实现方法registerBeanDefinitions。完成@FeignClient注解相关类的注入到ioc容器。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
  // 这个方式是注入一些配置,就是对 EnableFeignClients 注解属性的解析
	registerDefaultConfiguration(metadata, registry);
	// 这个方法是扫秒加了 @FeignClient 注解
	registerFeignClients(metadata, registry);
}

@FeignClient相关类的扫描,注入

  只记录常用的方式,其他可以看源码。这里只关注通过组件扫描注入。组件扫描用到类
ClassPathScanningCandidateComponentProvider,可以重写类的isCandidateComponent方法。完成相关组件的扫描,并封装为beanDefinition。

protected ClassPathScanningCandidateComponentProvider getScanner() {
		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
			@Override
			protected boolean isCandidateComponent(
					AnnotatedBeanDefinition beanDefinition) {
				boolean isCandidate = false;
				if (beanDefinition.getMetadata().isIndependent()) {
					if (!beanDefinition.getMetadata().isAnnotation()) {
						isCandidate = true;
					}
				}
				return isCandidate;
			}
		};
	}

  在openFeign中,只是扫描过滤了,非注解等相关类。通过设置IncludeFilter过滤,过滤类型是注解过滤FeignClient

ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
		Set basePackages = getBasePackages(metadata);
		for (String basePackage : basePackages) {
			candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
		}

  扫描范围为,通过方法getBasePackages获取跟包。逻辑主要是,标注注解EnableFeignClients的属性value,basePackages,basePackageClasses配置的内容。如果未配置,默认为当前类所在的包。我的理解是标注注解EnableFeignClients的类的包。

protected Set getBasePackages(AnnotationMetadata importingClassMetadata) {
		Map attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

		Set basePackages = new HashSet<>();
		for (String pkg : (String[]) attributes.get("value")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (Class clazz : (Class[]) attributes.get("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		if (basePackages.isEmpty()) {
			basePackages.add(
					ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

  扫描并返回对应类的beanDefinition后,遍历。获取注解FeignClient上的属性信息,通过BeanDefinitionBuilder构建类型是FeignClientFactoryBean的beanDefinition。并注册到BeanDefinitionRegistry中。FeignClientFactoryBean是一个工厂bean,可以通过getObject获取到@FeignClient注解代理的Bean。

补充:2023-06-15。扫描到@FeignClient标注的类,封装为beanDefinition后做以下操作。

public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
 
    Set basePackages;
 
    Map 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 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 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 attributes = annotationMetadata
              .getAnnotationAttributes(
                  FeignClient.class.getCanonicalName());
 
          String name = getClientName(attributes);
          registerClientConfiguration(registry, name,
              attributes.get("configuration"));
 
          registerFeignClient(registry, annotationMetadata, attributes);
        }
      }
    }
  }

  1. registerClientConfiguration(registry, name,attributes.get(“configuration”));根据当前被@FeignClient标注的类,注解上标注的configuration配置属性。构造FeignClient规范类:FeignClientSpecification

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(
        name + "." + FeignClientSpecification.class.getSimpleName(),
        builder.getBeanDefinition());
  }

  2. registerClientConfiguration(registry, name,attributes.get(“configuration”));根据当前被@FeignClient标注的类,注解上的属性信息。构造FeignClientFactoryBean类型的类。FeignClientFactoryBean是一个FactoryBean。可以通过getObject()获取实际对象。

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
 
    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 
    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
                                // null
 
    beanDefinition.setPrimary(primary);
 
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
      alias = qualifier;
    }
 
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
        new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

补充:2023-06-15。FeignAutoConfiguration。

  从名字上看是Feign的自动装配配置类。重点看FeignContext。它获取了容器中的FeignClientSpecification。而FeignClientSpecification就是我们扫描@FeignClient后对每个@FeignClient标注的类,注解中标注configuration属性都封装为FeignClientSpecification。

@Autowired(required = false)
private List configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
}

FeignContext

  先看以下它的结构。

public class FeignContext extends NamedContextFactory {
  public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
  }
}

  FeignContext 继承了NamedContextFactory,构造的时候,传入了FeignClientsConfiguration

NamedContextFactory

  NamedContextFactory 的作用是用来进行配置隔离的ribbonfeign的配置隔离都依赖这个抽象类。
  配置隔离:每个@FeignClient注解都有这个属性configuration。可以进行独立的配置。这个类用来协助进行隔离每个客户端的配置。

public abstract class NamedContextFactory
		implements DisposableBean, ApplicationContextAware {

	private final String propertySourceName;

	private final String propertyName;

	private Map contexts = new ConcurrentHashMap<>();

	private Map configurations = new ConcurrentHashMap<>();

	private ApplicationContext parent;

	private Class defaultConfigType;

	public NamedContextFactory(Class defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

	public void setConfigurations(List configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
	}

	public Set getContextNames() {
		return new HashSet<>(this.contexts.keySet());
	}

	@Override
	public void destroy() {
		Collection values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			// This can fail, but it never throws an exception (you see stack traces
			// logged as WARN).
			context.close();
		}
		this.contexts.clear();
	}

	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

	protected String generateDisplayName(String name) {
		return this.getClass().getSimpleName() + "-" + name;
	}

	public  T getInstance(String name, Class type) {
		AnnotationConfigApplicationContext context = getContext(name);
		try {
			return context.getBean(type);
		}
		catch (NoSuchBeanDefinitionException e) {
			// ignore
		}
		return null;
	}

	public  ObjectProvider getLazyProvider(String name, Class type) {
		return new ClientFactoryObjectProvider<>(this, name, type);
	}

	public  ObjectProvider getProvider(String name, Class type) {
		AnnotationConfigApplicationContext context = getContext(name);
		return context.getBeanProvider(type);
	}

	public  T getInstance(String name, Class clazz, Class... generics) {
		ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
		return getInstance(name, type);
	}

	@SuppressWarnings("unchecked")
	public  T getInstance(String name, ResolvableType type) {
		AnnotationConfigApplicationContext context = getContext(name);
		String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type);
		if (beanNames.length > 0) {
			for (String beanName : beanNames) {
				if (context.isTypeMatch(beanName, type)) {
					return (T) context.getBean(beanName);
				}
			}
		}
		return null;
	}

	public  Map getInstances(String name, Class type) {
		AnnotationConfigApplicationContext context = getContext(name);

		return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
	}

	/**
	 * Specification with name and configuration.
	 */
	public interface Specification {

		String getName();

		Class[] getConfiguration();

	}

}

  成员变量的作用:
  contexts:一个客户端一个对应AnnotationConfigApplicationContext。key一般为@FeignClient中属性contextId,如果congtextId为空取value,name…。key的取值来源如下:

private String getClientName(Map client) {
		if (client == null) {
			return null;
		}
		String value = (String) client.get("contextId");
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("value");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("name");
		}
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("serviceId");
		}
		if (StringUtils.hasText(value)) {
			return value;
		}

		throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
				+ FeignClient.class.getSimpleName());
	}

  configurations: 一个客户端一个配置类的封装,对应到 Feign 的就是 FeignClientSpecification。
  parent:springboot 真正启动的就是这个 ApplicationContext。
  defaultConfigType:默认的配置类,对应 Feign 就是构造 FeignContext 是传入的 FeignClientsConfiguration

FeignClientsConfiguration

  FeignClientsConfiguration是构建FeignContext的默认配置类。里面配置了很多bean。这些bean都是生成Feign客户端动态代理所需要的。

Contract
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

  这个的主要作用是用来解析 @FeignClient 接口中每个方法使用的 springmvc 的注解的,这也就是为什么 FeignClient 可以识别 springmvc 注解的原因。

Feign.Builder
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
}

  用来构建动态代理的类,通过这个类的 target 方法,就能生成 Feign 动态代理

FeignClientFactoryBean

  上面介绍的,注解@FeignClient标注的类,都被封装为FeignClientFactoryBean类型的beadDefinition注册到IOC。接下来看看,通过FeignClientFactoryBean如何获取到注解@FeignClient的代理对象,完成方法的调用。

// 通过getObject获取client对象。
@Override
public Object getObject() {
	return getTarget();
}
 T getTarget() {
		FeignContext context = beanFactory != null
				? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
       // @FeignClient注解中url属性不存在的业务逻辑
		if (!StringUtils.hasText(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();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}

       // @FeignClient注解中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 LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			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));
	}

contexts,每一个@FeignClient标注的接口都有自己的应用上下文的理解(下面会用到)

   通过工厂类获取目标对象的时候,首先获取FeignContext对象。FeignContext是用于创建和管理Feign Client所依赖的各种类的工厂类
  每个Feign Client会关联一个AnnotationConfigApplicationContext实例,用于存取Feign Client所依赖的各种类的实例
  (1)configurations中有根据name配置到的配置类,注册到AnnotationConfigApplicationContext。name = contextId-->value-->name-->serviceId(优先级从前到后,哪个属性不为空) + "FeignClientSpecification";
  (2)default默认的配置类,注册到AnnotationConfigApplicationContext。默认的配置类是@EnableFeignClients注解中属性defaultConfiguration中配置的配置类
  (3)注册PropertyPlaceholderAutoConfiguration
  (4)设置parent为当前服务应用上下文
  (5)刷新应用上下文

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

  注册AnnotationConfigApplicationContext,首先是注册服务接口的配置类,其次是注册全局的配置类,最后是注册默认的配置类defaultConfigType。defaultConfigType是FeignContext类实例化默认传入的FeignClientsConfiguration.class。
springcloud-openFeign简单梳理_第2张图片
  每个FeignClient注解标注的接口,对应的AnnotationConfigApplicationContext属性,allowEagerClassLoading = true。也就是说后加载的允许覆盖前面加载的类。
springcloud-openFeign简单梳理_第3张图片

configurations

  configurations中保存了每个Feign Client所依赖的配置类,在创建AnnotationConfigApplicationContext的过程中,这些配置类会被注入到Bean工厂中。

Feign.Builder

  用于构建feign对象。

  1. 通过ContextId获取到对应的AnnotationConfigApplicationContext实例。获取Feign.Builder对象。
  2. Feign.Builder的build方法创建feign对象ReflectiveFeign
  3. ReflectiveFeign对象的newInstance方法返回的就是一个代理对象,代理对象的InvocationHandler实现类就是ReflectiveFeign对象的内部类FeignInvocationHandler。

@FeignClient中url属性值:存在

  // @FeignClient注解中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 LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			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));

拼接请求路径。

  通过url,path拼接路径。lg:http://… + /path;

获取client

  通过方法getOptional获取client。

Client client = getOptional(context, Client.class);
  1. 通过FeignContext的getInstance方法,获取到contextId对应的应用上下文applicationContext。
  2. 在这个应用上下文中获取到类型为Client.class的bean对象。并返回对象。
  3. 根据client所实现的接口类型,转换为对应的类型。
  4. 相同的方式,通过contextId获取类型为Targeter.class的bean对象。
  5. 调用Targeter的target方法,默认是调用Feign.build的build()方法创建ReflectiveFeign对象,调用对象的newInstance方法,创建代理对象。返回代理@FeignClient标注接口的代理对象。

  方法执行,调用代理对象ReflectiveFeign中的内部类FeignInvocationHandler的invoke方法。根据Method找到对应的MethodHandler。默认实现是SynchronousMethodHandler。

  1. 方法之前前,通过RequestInterceptor对请求进行拦截处理。
  2. 调用client.execute方法,请求调用。
  3. 结果封装

  方法封装为DefaultMethodHandler 或者 SynchronousMethodHandler。是根据条件判断。

  for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
       // 方法描述符除了public还有其他的,设置为DefaultMethodHandler 
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
         // 方法描述符只有public,设置为SynchronousMethodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }

  Util.isDefault(method)方法如下:

public static boolean isDefault(Method method) {
    // Default methods are public non-abstract, non-synthetic, and non-static instance methods
    // declared in an interface.
    // method.isDefault() is not sufficient for our usage as it does not check
    // for synthetic methods. As a result, it picks up overridden methods as well as actual default
    // methods.
    final int SYNTHETIC = 0x00001000;
    return ((method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)
        && method.getDeclaringClass().isInterface();
  }

  (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)的含义是当前方法method的修饰符占abstract,public,static…这几个中的哪几个。是否只有public。

@FeignClient中url属性值:不存在

  @FeignClient中url为空,则url默认 = http:// + name + path;
  通过loadBalance方法构建请求代理对象。loadBalance。

loadBalance

  1. 通过FeignContext,获取到当前contextId对应的applicationContext。
  2. 通过applicationContext获取类型为client.class的实现类。
  3. 通过Feign.build设置client为通过applicationContext获取到的client。
  4. 通过applicationContext获取类型为Targeter.class的实现类。
  5. 调用Targeter.target方法获取到代理对象。

配置文件

feign: 
 client: 
   defaultToProperties: false # 是否以配置文件中配置为主。默认为true
   config: #对应FeignClientProperties类的config成员变量
    default: 
      # 日志级别
      logger-level: BASIC
      # 超时时间
      connect-timeout: 10000

springcloud-openFeign简单梳理_第4张图片
  将config下的配置封装为config = Map。FeignClientConfiguration封装上面的配置。lg:日志级别,超时时长。Map中的key为@FeignClient注解属性contextId。默认为default,也可以为没有给Feign接口配置对应的参数。
  配置文件中的参数优先级别。feign接口级别的配置 --> 默认配置

请求参数体封装

  入口在ReflectiveFeign的newInstance方法。
springcloud-openFeign简单梳理_第5张图片

ParseHandlersByName

  ParseHandlersByName是Feign.build的build()方法的时候构建的。
springcloud-openFeign简单梳理_第6张图片
  openFeign中的contract默认是SpringmvcContract,支持mvc注解的解析。
  ParseHandlersByName.apply方法。调用contract的parseAndValidateMetadata方法。解析获取到Feign接口的methodMetaData集合。然后遍历方法的源数据,构造Map对象,key:feign的接口名 + "#" + 方法名 + "(" + parameterTypeName集合 + ")"
value:SynchronousMethodHandler对象。对象里面包含MethodMetadata对象。
   我们知道,Feign接口代理的执行,最终会调用SynchronousMethodHandler.invoke方法。SynchronousMethodHandler对象中保存了client对象和方法的源信息。就可以进行业务的调用。

springcloud-openFeign简单梳理_第7张图片

SpringMvcFeignContract

  SpringMvcFeignContract继承SpringMvcContract。遍历Feign接口中的Method,调用Contract的parseAndValidateMetadata方法,解析封装为MethodMetadata。遍历MethodMetadata,对template进行处理。最下面,将 buildTemplate传给SynchronousMethodHandler对象。
springcloud-openFeign简单梳理_第8张图片
  contract.parseAndValidateMetadate(target.type())方法解析封装MethodMetaData。落地实现在Contract接口的内部类BaseContract中的方法parseAndValidateMetadata。
springcloud-openFeign简单梳理_第9张图片

springcloud-openFeign简单梳理_第10张图片

  方法执行时,调用SynchronousMethodHandler的invoke方法,invoke方法中调用buildTemplate的create方法,根据入参构建RequestTemplate对象。
springcloud-openFeign简单梳理_第11张图片

  调用executeAndDecodej进行请求调用和编码处理。
  (1)获取RequestInteceptor,对template进行请求前拦截。
  (2)通过调用client.execute(request, options),发出请求。
springcloud-openFeign简单梳理_第12张图片

OpenFeign接口多继承异常问题

  Only single-level inheritance supported
  异常是由feign.Contract.BaseContract.parseAndValidatateMetadata抛出来的。
springcloud-openFeign简单梳理_第13张图片

  可以通过自定义Contract的实现。替换掉原来默认的实现。

OpenFeign接口中是否可以传多个body?

  默认只允许一个注解@RequestBody。不加注解的参数默认@RequestBody。多个会有异常:Method has too many Body parameters

FeignClientFactoryBean#getObject链路

Contract解析MethodMetadata

  自定义openfeign接口如下:

@FeignClient(contextId = "songProvider", name = "song-provide-testOne", url = "http://127.0.0.1:8089/")
public interface FeignTest {

    @GetMapping("/song/getTest.json")
    List getTest(
            @RequestHeader("tenant_id") String tenantId,
            @RequestParam("equipmentIds") List equipmentIds
    );

    @PostMapping("/song/postTest.json")
    List postTest(
            @RequestHeader("tenant_id") String tenantId,
            @RequestBody List collectorChannelIds
    );
}

getTest方法的MethodMetadata如下:

springcloud-openFeign简单梳理_第14张图片
  requetTemplate里面的methodMetadata引用的是上面的自身的methodMetadata。
springcloud-openFeign简单梳理_第15张图片
springcloud-openFeign简单梳理_第16张图片

postTest方法的MethodMetadata如下:

springcloud-openFeign简单梳理_第17张图片
  我们可以看到@RequestBody参数。在MethodMetadata的属性为。索引位置:bodyIndex。body类型:bodyType。
springcloud-openFeign简单梳理_第18张图片
springcloud-openFeign简单梳理_第19张图片

请求过程记录

发起getTest请求

  根据请求参数构建新的RequestTemplate对象。
springcloud-openFeign简单梳理_第20张图片
springcloud-openFeign简单梳理_第21张图片
  新的RequestTemplate结构如下:
springcloud-openFeign简单梳理_第22张图片
  根据indexToName方法获取需要赋值的参数,根据索引从实体类中获取对应的值。
springcloud-openFeign简单梳理_第23张图片
  之前的结构:
springcloud-openFeign简单梳理_第24张图片
  数据赋值后的结构:
springcloud-openFeign简单梳理_第25张图片
  获取到的options:
springcloud-openFeign简单梳理_第26张图片

代理对象创建过程

springcloud-openFeign简单梳理_第27张图片

路由

  再FeignClientFactoryBean中getObject方法的时候,如果uri参数为空,通过loadBalance方法获取到client的实现类:LoadBalancerFeignClient。如果uri不为空,将获取到的client进行类型转换。
springcloud-openFeign简单梳理_第28张图片

  再通过SynchronousMethodHandler调用方法执行的时候,执行的是LoadBalancerFeignClient的executor方法。

你可能感兴趣的:(SpringCloud,spring,cloud)