OpenFeign,声明式的伪RPC调用(二)

262 阅读14分钟

文章目录

一、前言

feign源码中要完成的事情,如下:
1、参数的解析和装载;
2、针对指定的feignClient生成动态代理;
3、针对FeignClient中的方法描述进行动态解析;
4、组装成一个request对象发起请求。

二、参数的解析和装载 + 针对指定的feignClient生成动态代理

2.1 从FeignClientFactoryBean.getObject()出发…

接上一篇博文OpenFeign,声明式的伪RPC调用(一),这里,我们从 FeignClientFactoryBean.getObject()出发,如下:

FeignClientFactoryBean.java

@Override
public Object getObject() throws Exception {
	return getTarget();
}

2.1.1 getBean得到容器中的FeignContext实例

getObject调用了getTarget方法,如下:

FeignClientFactoryBean.java

<T> T getTarget() {
    // 这里通过getBean方法,从ioc容器中拿到FeignContext bean,其实用@Autowired也是一样的
	FeignContext context = this.applicationContext.getBean(FeignContext.class); 
    ...
}

在getTarget方法中,第一行代码就是从IOC容器中拿到FeignContext实例,那么,FeignContext从哪里来的,什么时候放到IOC容器中的?

根据类名或接口名从ioc容器中拿到指定的bean,一般包括两种方式getBean(Xxx.class)和@Autowired

让我们来见识一下这个FeignContext的类结构示意图

在这里插入图片描述

可以看到,这个FeignContext继承于NamedContextFactory,如下:

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

现在我们的问题是:我们通过getBean取到FeignContext这个bean,那么,这个bean是什么时候被放到ioc容器中的呢?

在FeignContext类的定义中,并没有什么注解,找到FeignAutoConfiguration类,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();
	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();  // new一个FeignContext
		context.setConfigurations(this.configurations); // 新建的context设置configuration
		return context;  // 返回新建的context,新建的FeignContext对象
	}
	...
}

可以看到,上述源码中,configurations是一个list,里面存放FeignClientSpecification,根据每一个feignClient隔离(就是每一个方法隔离),放到list里面,这个list最后会作为一个configurations属性设置到一个FeignContext对象里面,这个FeignContext对象作为一个bean放到ioc容器里面。

2.1.2 feign方法

在自动装配类里面,定义了FeignContext,然后就会装配到ioc容器中,就可以用getBean取出来了,就解释通了,回到FeignClientFactoryBean.getTarget方法,看看从ioc容器中取到FeignContext实例中,接下来做什么?

FeignClientFactoryBean.java

<T> T getTarget() {
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	 // 看到这行代码,从ioc容器中拿到的FeignContext对象bean怎么操作
	Feign.Builder builder = feign(context); 
    ...
}

接下来执行了feign方法,那么,我们看到feign方法,如下:

protected Feign.Builder feign(FeignContext context) {
    // 得到一个Feign的日志工厂对象
	FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); 
	Logger logger = loggerFactory.create(this.type);   // 从日志工厂中生产出日志对象

	// @formatter:off
	Feign.Builder builder = get(context, Feign.Builder.class)  // 得到Feign.Builder对象
			.logger(logger)   // 设置Feign.Builder对象的logger属性,不同日志级别
			.encoder(get(context, Encoder.class)) // 设置Feign.Builder对象的encoder编码属性
 			.decoder(get(context, Decoder.class)) // 设置Feign.Builder对象的decoder编码属性
			.contract(get(context, Contract.class)); // 设置Feign.Builder对象的contract编码属性,解析模板之用

	configureFeign(context, builder); // 配置
 
	return builder;  // 返回刚刚搞好的Feign.Builder对象
}

protected void configureFeign(FeignContext context, Feign.Builder builder) {
    // getBean中取到FeignClientProperties的bean
	FeignClientProperties properties = this.applicationContext
			.getBean(FeignClientProperties.class);

	FeignClientConfigurer feignClientConfigurer = getOptional(context,
			FeignClientConfigurer.class);
	setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

	if (properties != null && inheritParentContext) {
		if (properties.isDefaultToProperties()) {
			configureUsingConfiguration(context, builder);
			configureUsingProperties(
					properties.getConfig().get(properties.getDefaultConfig()),
					builder);
			configureUsingProperties(properties.getConfig().get(this.contextId),
					builder);
		}
		else {
			configureUsingProperties(
					properties.getConfig().get(properties.getDefaultConfig()),
					builder);
			configureUsingProperties(properties.getConfig().get(this.contextId),
					builder);
			configureUsingConfiguration(context, builder);
		}
	}
	else {
		configureUsingConfiguration(context, builder);
	}
}

在这其中被调用的get方法,传进来实参FeignContext对象bean,和一个Class字节码对象,名为type。逻辑:从这个context中,根据服务名称得到实例。

2.1.3 loadBalancer方法

不扯远了,再次回到getTarget方法,可以看到,执行完getBean和feign方法之后,getTarget方法中的主要逻辑就是执行一个loadBalancer方法,如下:

FeignClientFactoryBean.java

<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();
		// 进入loadBalance方法
		return (T) loadBalance(builder, context,
				new HardCodedTarget<>(this.type, this.name, this.url));
	}
    ...
}

看一下loadBalancer方法具体干了些什么,如下:

2.2 loadBalancer方法

2.2.1 getOptional方法得到一个LoadBalancerFeignClient对象

FeignClientFactoryBean.java

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
		HardCodedTarget<T> target) {
    // 这里的client是LoadBalanceFeignClient对象
	Client client = getOptional(context, Client.class);
	if (client != null) {
		builder.client(client);  // 这里的client是LoadBalanceFeignClient对象
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);  // 返回值
	}
}

在FeignClientFactoryBean类的loadBalancer方法中,第一步就是得到一个client,其实这里的client是一个LoadBalanceFeignClient对象,是一个针对于负载均衡的FeignClient客户端,它的定义如下:

LoadBalancerFeignClient.java  针对于负载均衡的FeignClient客户端

public class LoadBalancerFeignClient implements Client {

我们找到FeignRibbonClientAutoConfiguration,在这个配置类中,导入了一个默认Feign负债均衡配置类,且看:

FeignRibbonClientAutoConfiguration.java

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
		matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })  // 这里导入一个默认Feign负载均衡配置类
public class FeignRibbonClientAutoConfiguration {
   ...
} 

这个DefaultFeignLoadBalancedConfiguration类是干什么的呢?我们看到它的定义,如下:

DefaultFeignLoadBalancedConfiguration.java

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
			// 这个配置类中顶定义了LoadBalancerFeignClient这个bean
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

}

所以,这个默认Feign负载均衡配置类中,想IOC容器中注入了一个LoadBalancerFeignClient对象,其返回类型是Client,这样写是没关系的,因为LoadBalancerFeignClient实现了Client接口。

2.2.2 get方法通过服务名称得到服务实例

我们重新回到FeignClientFactoryBean.loadBalance方法,如下:

FeignClientFactoryBean.java

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
		HardCodedTarget<T> target) {
    // 这里的client是LoadBalanceFeignClient对象
	Client client = getOptional(context, Client.class);
	if (client != null) {
		builder.client(client);  // 这里的client是LoadBalanceFeignClient对象
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);  // 返回值
	}
}

FeignClientFactoryBean.loadBalance方法,通过getOptional方法得到一个LoadBalancerFeignClient对象后,然后执行了get方法,那么这个get方法是干什么的?打开定义,如下:

FeignClientFactoryBean.java

protected <T> T get(FeignContext context, Class<T> type) {
    // 这里的contextId就是服务名称,如"spring-cloud-order-service",根据服务名称得到一个实例   
    // contexts map中有的话直接拿出来,context map没有就底层会新建了一个
	T instance = context.getInstance(this.contextId, type);  
	if (instance == null) {
		throw new IllegalStateException(
				"No bean found of type " + type + " for " + this.contextId);
	}
	return instance;
}

get方法执行一个context.getInstance方法,入参是contextId,返回值是泛型,是一个instance,这个context.getInstance方法就是根据服务名称取出服务实例,我们看看是不是这样,下面是getInstance方法,注意事NameContextFactory类中的getInstance方法哦,因为FeignContext是NameContextFactory的子类。

在这里插入图片描述

NameContextFactory.java

public <T> T getInstance(String name, Class<T> type) {
    // 看看这个getContext方法
	AnnotationConfigApplicationContext context = getContext(name);
	if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
			type).length > 0) {
		return context.getBean(type);
	}
	return null;
}
// 这是一个ConcurrentHashMap,内部锁分段技术,保证并发安全的map
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

protected AnnotationConfigApplicationContext getContext(String name) {
    // if + synchronized + if保证线程安全
	if (!this.contexts.containsKey(name)) {
		synchronized (this.contexts) {
			if (!this.contexts.containsKey(name)) {
			    // 如果类变量contexts这么map里面没有这个key,就新建一个然后put进去
				this.contexts.put(name, createContext(name));
			}
		}
	}
	return this.contexts.get(name);
}

protected AnnotationConfigApplicationContext createContext(String name) {
    // 源码中直接new了一个context,然后对其设置
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ...
	// 最后返回新建的context上下文对象
	return context;
}

我们看到,在FeignContext中,就是通过contexts这个类变量来分割不同服务名称的不同的服务实例的,画图如下所示:
在这里插入图片描述

2.2.3 targeter.target方法

好了,我们再次回到FeignClientFactoryBean类的loadBalancer方法,通过getOptional方法得到一个LoadBalancerFeignClient对象后,然后执行了get方法通过服务名称得到服务实例后,最后执行targeter.target方法,如下:

FeignClientFactoryBean.java

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
		HardCodedTarget<T> target) {
     ...
     // 关于loadBalance方法,看一下这个target方法
	 return targeter.target(this, builder, context, target);  // 返回值
}

idea看一个方法的实现:ctrl + alt + B,这里targeter有两个实现可以选择,这里不是HystrixTargeter,因为我们这里不是熔断,进入到DefaultTargeter类的target方法中,如下:

DefaultTargeter.java

class DefaultTargeter implements Targeter {
   
	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		// 进去到Feign类的target方法
		return feign.target(target);
	}

}

2.3 newInstance方法

按照要求进入到Feign.target方法中,如下:

Feign.java   最终到Feign.target,返回一个实例

public <T> T target(Target<T> target) {
  // 进入同类的build方法
  return build().newInstance(target);
}

我们想看看这个build()方法返回的是什么,如下:

Feign.java

public Feign build() {
  Client client = Capability.enrich(this.client, capabilities);  // client
  Retryer retryer = Capability.enrich(this.retryer, capabilities); //retryer
  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); // contract编码属性,解析模板之用
  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);
          // 这里返回一个ReflectiveFeign对象,所以看ReflectiveFeign.newInstance方法
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

我们看到,Feign.build()返回的其实是一个new ReflectiveFeign对象,所以我们进入到ReflectiveFeign.newInstance()方法,如下:

ReflectiveFeign.java

  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()) {
      ...
    }
    // java里面典型的动态代理实现  这个handler是一个FeignInvocationHandler,这个要debug可以知道
    // 也可看源码知道
    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;
  }

我们看到ReflectiveFeign类中的newInstance方法,最后几行代码就是使用java动态代理生成一个代理对象返回出去。

补充一下,我们看看factory.create创建的具体是一个什么handler对象。

方法1:从源码中看出handler是一个FeignInvocationHandler

这里我们看到,create方法返回的是一个new ReflectiveFeign.FeignInvocationHandler对象,如下

InvocationHandlerFactory接口的Default内部类

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      // 这个dispatch是一个Map<Method, MethodHandler>
      // 来源于上面的实参methodToHandler,就是(method,handler)键值对
      // 就是ReflectiveFeign.newInstance方法,就是它的实参target
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

所以,ReflectiveFeign.newInstance方法中,最后的动态代理,返回的handler是一个FeignInvocationHandler对象,不用断点运行起来也可以知道。

方法2:debug调试看出handler是一个FeignInvocationHandler

ReflectiveFeign.FeignInvocationHandler.java
// 在这个invoke方法中断点,启动springboot工程的时候可以进来
// 则说明上面的handler实际上是一个FeignInvocationHandler
@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();
  }
  // 选择执行哪个方法
  return dispatch.get(method).invoke(args);
}

最后,我们知道,主代码中@Autowired取得的是一个代理对象

@RestController   // 返回字符串
public class OpenFeignController {
    @Autowired   // 前提条件是spring ioc容器中存在这个
    private OrderServiceFeignClient orderServiceFeignClient;  
    // 动态代理,这里用@Autowired取得的是一个动态代理对象,也可以用getBean取出ioc容器中对象
    // 
    @GetMapping("/testFeign")
    public String test() {
        return orderServiceFeignClient.getAllOrder();
    }
}

2.4 小结:从主代码@Autowired到Feign源码中动态代理整个过程

小结:整个过程就是

ReflectiveFeign.newInstance
Feign.target
DefaultTargeter.target
FeignClientFactoryBean.loadBalance
FeignClientFactoryBean.getTarget
FeignClientFactoryBean.getObject

问题:FeignClientFactoryBean.getObject是什么时候被调用的?
上一篇博客解释了呀,springboot启动类上使用@EnableFeignClients注解,这个注解的定义上使用了@Import(FeignClientsRegistrar.class),然后,FeignClientsRegistrar又实现了ImportBeanDefinitionRegistrar接口。

spring启动的时候也一定会执行到FeignClientsRegistrar类的registerBeanDefinitions()方法,那个这个方法里面执行了什么呢,我们看到,就是

FeignClientsRegistrar.java

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	registerDefaultConfiguration(metadata, registry);
	registerFeignClients(metadata, registry);
}

public void registerFeignClients(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
    ...
	registerFeignClient(registry, annotationMetadata, attributes);
}

private void registerFeignClient(BeanDefinitionRegistry registry,
		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
	BeanDefinitionBuilder definition = BeanDefinitionBuilder
			.genericBeanDefinition(FeignClientFactoryBean.class);  // 这里新建一个factorybean
	... 
}
class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware 

既然是factorybean,实现了factorybean接口,所以一定会执行FeignClientFactoryBean类的getObject方法,所以这就是本文所讲的了,最后使用java动态代理返回一个代理对象。

三、针对FeignClient中的方法描述进行动态解析

3.1 针对FeignClient中的方法描述进行动态解析

在上一篇博文中,我们说到,从feign的功能上来说,它需要完成四件事:

1、参数的解析和装载(已完成);
2、针对指定的feignClient,生成动态代理(已完成);
3、针对FeignClient中的方法描述进行动态解析(尚未完成);
4、最后,组装成一个request对象发起请求(尚未完成)。

Feign源码中是如何完成“针对FeignClient中的方法描述进行动态解析”,看到刚才的ReflectiveFeign.newInstance 方法,它先执行了一个apply方法,将主代码中的方法声明解析为一些MethodMetadata对象,放到一个List< MethodMetadata>里面,然后遍历这个list,对于每一个md(metadata),使用factory.create变为methodHandler,放到Map<String, MethodHandler>里面,最后返回这个Map<String, MethodHandler> result,它携带着解析好的MethodHandler。

ReflectiveFeign.java

  public <T> T newInstance(Target<T> target) {
    // 根据实参target,得到一个存放MethodHandler的map,名为nameToHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    ...
  }
  // 同类的中apply方法
    public Map<String, MethodHandler> apply(Target target) {
      // 这个contractFeign.Builder对象的contract编码属性,解析模板之用
      // 所谓的解析就是将主代码中的方法声明解析为一些MethodMetadata对象
      // 得到结果放到这个名为metadata的list列表中,代码中看不出来,debug可以知道
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      // 新建一个map,名为result,用来存放返回结果
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      // 遍历解析出来的list列表
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;  // 声明一个BuildTemplateByResolvingArgs对象
        // 给这个bulidTemplate赋值
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        // 结果放到result中
        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;  // 返回这个result
    }
  }

再到ReflectiveFeign.newInstance方法中来,

ReflectiveFeign.java

  public <T> T newInstance(Target<T> target) {
    ...
    // 新建一个map
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // 新建一个list
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    // 遍历实参target中的所有的method
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {  
        continue;  // 编译类型为Object,跳过
      } else if (Util.isDefault(method)) {  // 是默认method
        // 根据method新建一个Handler
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        // 将得到的handler放到list里面
        defaultMethodHandlers.add(handler);
        // 将遍历中(method,handler)放到methodToHandler这个map中
        methodToHandler.put(method, handler);  
      } else {  // 不是默认method
        // handler不能直接放DefaultMethodHandler,要从nameToHandler中取出,根据key取出
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    ...
  }

ReflectiveFeign.newInstance方法中,将所有的(Method, MethodHandler)放在methodToHandler变量中后,执行动态代理就是上面说到的,不说了

ReflectiveFeign.newInstance方法

// java里面典型的动态代理实现  这个handler是一个FeignInvocationHandler,这个要debug才能知道
// 将target和存有所有(method,handler)键值对的methodToHandler作为实参,放到factory.create
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;

至此,完成第三步,针对FeignClient中的方法描述进行动态解析。

3.2 补充:类变量contract运行时类型是SpringMvcContract

看一些补充内容,在对方法描述动态解析的过程中,在ReflectiveFeign.apply方法中,用到了contract.parseAndValidateMetadata(target.type()); 这是方法解析的关键,这个contract默认是一个SpringMvcContract类对象,其父类是Contract.BaseContract,在上去才是接口Contract,这也是contract变量在ReflectiveFeign类中的的编译类型,相关代码如下:

SpringMvcContract.java

// Feign.Builder类中的contract默认是SpringMvcContract类,从代码中看不出来,debug就可以知道
public class SpringMvcContract extends Contract.BaseContract
		implements ResourceLoaderAware {
BaseContract.java

  abstract class BaseContract implements Contract {

在ReflectiveFeign类中,类变量contract运行时类型是SpringMvcContract,编译时类型是Context,编译时类型通过看代码就知道,运行时类型断点ReflectiveFeign.apply方法的第一行代码,然后启动springboot工程,就可以知道了。

3.3 调用方法

解析完参数后就好调用方法,找到ReflectiveFeign.FeignInvocationHandler.invoke方法,如下:

ReflectiveFeign.FeignInvocationHandler.java
// 在这个invoke方法中断点,启动springboot工程的时候可以进来
// 则说明上面的handler实际上是一个FeignInvocationHandler
@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();
  }
  // 选择执行哪个方法  进入invoke方法
  return dispatch.get(method).invoke(args);
}

看一下这个invoke方法,

SynchronousMethodHandler.java 

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 到这里,feign的四要素中,请求类型(get/post)子路径(/orders)方法名称、方法参数拿到了(debug就可以知道),只差根据服务名称spring-cloud-order-service解析出ip:port,只差解析服务名称了,这是ribbon的工作,feign工作做完了
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    ...
    }
  }

3.4 补充:dispatch是一个Map< Method, MethodHandler>

再补充一点,关于dispatch变量的,在ReflectiveFeign.FeignInvocationHandler类的invoke方法中,最后一行是从dispatch中得到一个方法,然后传入参数进行调用。

ReflectiveFeign.FeignInvocationHandler.java

// 在这个invoke方法中断点,启动springboot工程的时候可以进来
// 则说明上面的handler实际上是一个FeignInvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  ...
  // 选择执行哪个方法,调用这个方法的invoke方法
  return dispatch.get(method).invoke(args);
}

关于这个dispatch,本质上是一个Map<Method, MethodHandler>,如下:

ReflectiveFeign.FeignInvocationHandler.java

private final Target target;
private final Map<Method, MethodHandler> dispatch;  // dispatch变量声明

FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
  this.target = checkNotNull(target, "target");
  this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}

四、组装成一个request对象发起请求

第四步,发送request请求,看到SynchronousMethodHandler的invoke方法的后半段(发送request),刚才是前半段(针对FeignClient中的方法描述进行动态解析),如下

SynchronousMethodHandler.java 

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    ...
    while (true) {
      try {
        return executeAndDecode(template, options);  // 进去
      } catch (RetryableException e) {
        ...
      }
    }        
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);  // 进去
    Response response;
    long start = System.nanoTime();  //记录开始时间
    try {
 // 因为这个client是一个LoadBalancerFeignClient对象,所以转到LoadBalanceFeignClient.execute方法
      response = client.execute(request, options);  // 发出request请求
      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;
    }
  }

  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }
LoadBalancerFeignClient.java

public Response execute(Request request, Request.Options options) throws IOException {
	try {
	   // 断点此处,启动springboot工程,还是服务名不是ip:port
	   // asUri变量为 http://order-service/orders ; clientName为order-service
		URI asUri = URI.create(request.url());  // url 
		String clientName = asUri.getHost(); // host
		URI uriWithoutHost = cleanUrl(request.url(), clientName);
		// 服务名称解析为ip:port不是又feign完成,是由ribbon完成
		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) {
      ... // 没什么用,省略
	}
}

五、小结

在这里插入图片描述

上图比较复杂,一点点来解释:

开发者通过@Autowired 拿到的XxxFeignClient实例是FeignClientFactoryBean.getObject()提供的,然后FeignClientFactoryBean.getObject()底层是调用FeignClientFactoryBean.getTarget(),这个调用链条是:

FeignClientFactoryBean.getObject
FeignClientFactoryBean.getTarget
FeignClientFactoryBean.loadBalance
DefaultTargeter.target
Feign.target
ReflectiveFeign.newInstance

好了,到了ReflectiveFeign.newInstance,在这个方法中,生成了一个FeignInvocationHandler的动态代理对象,最后再用handler构造一个proxy返回给开发者的@Autowired,这就是对于图的左上部分的解释,完成了。

看右上部分,会对方法就行拦截,拦截的目的是对于服务名称/服务实例进行解析,获得最终的url地址,即ip:port,这是通过ribbon来完成的。然后右下方式获得url地址后,发起一个http通信。

左下方这里是一个补充,就是

其实,我们可以将feign的整个过程简单化,如下:

在这里插入图片描述

所以说,为什么Feign被称为伪RPC,因为它具备一个RPC通信所有必须的条件,动态代理、编码解码、序列化和反序列化、消息路由、底层请求的实现。

关于feign和ribbon的一个问题:能不能只是用feign不使用ribbon?
回答:对于开发者来说,无法做到只使用feign不使用ribbon,因为导入feign的依赖的时候,里面就已经包含了ribbon的依赖,无法将ribbon依赖剔除出去,因为它已经封装在feign里面了;但是,对于开发者来说,可以只引入feign依赖不引入ribbon依赖,也是因为ribbon依赖已经封装在feign依赖里面。

六、尾声

OpenFeign,声明式的伪RPC调用(二),完成了。这篇博文接上一篇OpenFeign,声明式的伪RPC调用(一),对openfeign源码解析。

天天打码,天天进步!!