openFeign是springcloud中,服务间进行调用的常用方式。了解它,可以更好的处理服务间调用问题。
每个@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);
}
只记录常用的方式,其他可以看源码。这里只关注通过组件扫描注入。组件扫描用到类
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。
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);
}
从名字上看是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;
}
先看以下它的结构。
public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContext
继承了NamedContextFactory
,构造的时候,传入了FeignClientsConfiguration
。
NamedContextFactory 的作用是用来进行配置隔离的
。ribbon
和feign
的配置隔离都依赖这个抽象类。
配置隔离:每个@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是构建FeignContext的默认配置类。里面配置了很多bean。这些bean都是生成Feign客户端动态代理所需要的。
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
这个的主要作用是用来解析 @FeignClient 接口中每个方法使用的 springmvc 的注解的,这也就是为什么 FeignClient 可以识别 springmvc 注解的原因。
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
用来构建动态代理的类,通过这个类的 target 方法,就能生成 Feign 动态代理
上面介绍的,注解@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));
}
通过工厂类获取目标对象的时候,首先获取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。
每个FeignClient注解标注的接口,对应的AnnotationConfigApplicationContext属性,allowEagerClassLoading = true。也就是说后加载的允许覆盖前面加载的类。
configurations
中保存了每个Feign Client所依赖的配置类,在创建AnnotationConfigApplicationContext的过程中,这些配置类会被注入到Bean工厂中。
用于构建feign对象。
ReflectiveFeign
。 // @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;
通过方法getOptional获取client。
Client client = getOptional(context, Client.class);
ReflectiveFeign
对象,调用对象的newInstance
方法,创建代理对象。返回代理@FeignClient标注接口的代理对象。方法执行,调用代理对象ReflectiveFeign中的内部类FeignInvocationHandler的invoke方法。根据Method找到对应的MethodHandler。默认实现是SynchronousMethodHandler。
方法封装为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为空,则url默认 = http:// + name + path
;
通过loadBalance方法构建请求代理对象。loadBalance。
feign:
client:
defaultToProperties: false # 是否以配置文件中配置为主。默认为true
config: #对应FeignClientProperties类的config成员变量
default:
# 日志级别
logger-level: BASIC
# 超时时间
connect-timeout: 10000
将config下的配置封装为config = Map
配置文件中的参数优先级别。feign接口级别的配置 --> 默认配置
入口在ReflectiveFeign的newInstance方法。
ParseHandlersByName是Feign.build的build()方法的时候构建的。
openFeign中的contract默认是SpringmvcContract,支持mvc注解的解析。
ParseHandlersByName.apply方法。调用contract的parseAndValidateMetadata方法。解析获取到Feign接口的methodMetaData集合。然后遍历方法的源数据,构造Map对象,key:feign的接口名 + "#" + 方法名 + "(" + parameterTypeName集合 + ")"
。
value:SynchronousMethodHandler对象。对象里面包含MethodMetadata对象。
我们知道,Feign接口代理的执行,最终会调用SynchronousMethodHandler.invoke方法。SynchronousMethodHandler对象中保存了client对象和方法的源信息。就可以进行业务的调用。
SpringMvcFeignContract继承SpringMvcContract。遍历Feign接口中的Method,调用Contract的parseAndValidateMetadata方法,解析封装为MethodMetadata。遍历MethodMetadata,对template进行处理。最下面,将 buildTemplate传给SynchronousMethodHandler对象。
contract.parseAndValidateMetadate(target.type())方法解析封装MethodMetaData。落地实现在Contract接口的内部类BaseContract中的方法parseAndValidateMetadata。
方法执行时,调用SynchronousMethodHandler的invoke方法,invoke方法中调用buildTemplate的create方法,根据入参构建RequestTemplate对象。
调用executeAndDecodej进行请求调用和编码处理。
(1)获取RequestInteceptor,对template进行请求前拦截。
(2)通过调用client.execute(request, options),发出请求。
Only single-level inheritance supported
异常是由feign.Contract.BaseContract.parseAndValidatateMetadata抛出来的。
可以通过自定义Contract的实现。替换掉原来默认的实现。
默认只允许一个注解@RequestBody。不加注解的参数默认@RequestBody。多个会有异常:Method has too many Body parameters
自定义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
);
}
requetTemplate里面的methodMetadata引用的是上面的自身的methodMetadata。
我们可以看到@RequestBody参数。在MethodMetadata的属性为。索引位置:bodyIndex。body类型:bodyType。
根据请求参数构建新的RequestTemplate对象。
新的RequestTemplate结构如下:
根据indexToName方法获取需要赋值的参数,根据索引从实体类中获取对应的值。
之前的结构:
数据赋值后的结构:
获取到的options:
再FeignClientFactoryBean中getObject方法的时候,如果uri参数为空,通过loadBalance方法获取到client的实现类:LoadBalancerFeignClient。如果uri不为空,将获取到的client进行类型转换。
再通过SynchronousMethodHandler调用方法执行的时候,执行的是LoadBalancerFeignClient的executor方法。