Spring Cloud 源码分析之OpenFeign

179 篇文章 1 订阅
165 篇文章 3 订阅

OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式来实现远程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示。

在今天的内容中,我们需要详细分析OpenFeign它的工作原理及源码,我们继续回到这段代码。

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

 @Autowired
 IGoodsServiceFeignClient goodsServiceFeignClient;
 @Autowired
 IPromotionServiceFeignClient promotionServiceFeignClient;
 @Autowired
 IOrderServiceFeignClient orderServiceFeignClient;

 /**
  * 下单
  */
 @GetMapping
 public String order(){
  String goodsInfo=goodsServiceFeignClient.getGoodsById();
  String promotionInfo=promotionServiceFeignClient.getPromotionById();
  String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
  return result;
 }
}

从这段代码中,先引出对于OpenFeign功能实现的思考。

  1. 声明@FeignClient注解的接口,如何被解析和注入的?
  2. 通过@Autowired依赖注入,到底是注入一个什么样的实例
  3. 基于FeignClient声明的接口被解析后,如何存储?
  4. 在发起方法调用时,整体的工作流程是什么样的?
  5. OpenFeign是如何集成Ribbon做负载均衡解析?

带着这些疑问,开始去逐项分析OpenFeign的核心源码

OpenFeign注解扫描与解析

从这个结果来看,可以得到两个结论

  1. @FeignClient声明的接口,在Spring容器启动时,会被解析。
  2. 由于被Spring容器加载的是接口,而接口又没有实现类,因此Spring容器解析时,会生成一个动态代理类。

EnableFeignClient

@FeignClient注解是在什么时候被解析的呢?基于我们之前所有积累的知识,无非就以下这几种

  • ImportSelector,批量导入bean
  • ImportBeanDefinitionRegistrar,导入bean声明并进行注册
  • BeanFactoryPostProcessor , 一个bean被装载的前后处理器

在这几个选项中,似乎ImportBeanDefinitionRegistrar更合适,因为第一个是批量导入一个bean的string集合,不适合做动态Bean的声明。 而BeanFactoryPostProcessor 是一个Bean初始化之前和之后被调用的处理器。

而在我们的FeignClient声明中,并没有Spring相关的注解,所以自然也不会被Spring容器加载和触发。

在集成FeignClient时,我们在SpringBoot的main方法中,声明了一个注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")。这个注解需要填写一个指定的包名。

嗯,看到这里,基本上就能猜测出,这个注解必然和@FeignClient注解的解析有莫大的关系。

下面这段代码是@EnableFeignClients注解的声明,果然看到了一个很熟悉的面孔FeignClientsRegistrar

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

FeignClientsRegistrar

FeignClientRegistrar,主要功能就是针对声明@FeignClient注解的接口进行扫描和注入到IOC容器。

class FeignClientsRegistrar
  implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
 
}

果然,这个类实现了ImportBeanDefinitionRegistrar接口

public interface ImportBeanDefinitionRegistrar {
 default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
  this.registerBeanDefinitions(importingClassMetadata, registry);
 }

 default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 }
}

这个接口有两个重载的方法,用来实现Bean的声明和注册。

简单给大家演示一下ImportBeanDefinitionRegistrar的作用。

  1. HelloService.java
  2. GpImportBeanDefinitionRegistrar.java
  3. EnableGpRegistrar.java
  4. TestMain
  1. 定义一个需要被装载到IOC容器中的类HelloService
public class HelloService {
}
  1. 定义一个Registrar的实现,定义一个bean,装载到IOC容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

 @Override
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
          BeanDefinitionRegistry registry) {
  BeanDefinition beanDefinition=new GenericBeanDefinition();
  beanDefinition.setBeanClassName(HelloService.class.getName());
  registry.registerBeanDefinition("helloService",beanDefinition);
 }
}
  1. 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain {

 public static void main(String[] args) {
  ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
  System.out.println(applicationContext.getBean(HelloService.class));

 }
}
  1. 通过结果演示可以发现,HelloService这个bean 已经装载到了IOC容器。

这就是动态装载的功能实现,它相比于@Configuration配置注入,会多了很多的灵活性。 ok,再回到FeignClient的解析中来。

FeignClientsRegistrar.registerBeanDefinitions

  • registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有 @EnableFeignClients, 有该注解的话, 则完成Feign框架相关的一些配置内容注册。
  • registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的 BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition 
@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);
}
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
 registerFeignClient(registry, annotationMetadata, attributes);
}

FeignClientsRegistrar.registerFeignClients

registerFeignClients方法的定义如下。

//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
  BeanDefinitionRegistry registry) {
 
 LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
 //获取@EnableFeignClients注解的元数据
 Map<String, Object> attrs = metadata
   .getAnnotationAttributes(EnableFeignClients.class.getName());
 //获取@EnableFeignClients注解中的clients属性,可以配置@FeignClient声明的类,如果配置了,则需要扫描并加载。
 final Class<?>[] clients = attrs == null ? null
   : (Class<?>[]) attrs.get("clients");
 if (clients == null || clients.length == 0) {
  //默认TypeFilter生效,这种模式会查询出许多不符合你要求的class名
  ClassPathScanningCandidateComponentProvider scanner = getScanner();
  scanner.setResourceLoader(this.resourceLoader);
  scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); //添加包含过滤的属性@FeignClient。
  Set<String> basePackages = getBasePackages(metadata); //从@EnableFeignClients注解中获取basePackages配置。
  for (String basePackage : basePackages) {
   //scanner.findCandidateComponents(basePackage) 扫描basePackage下的@FeignClient注解声明的接口
   candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //添加到candidateComponents,也就是候选容器中。
  }
 }
 else {//如果配置了clients,则需要添加到candidateComponets中。
  for (Class<?> clazz : clients) {
   candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
  }
 }
 //遍历候选容器列表。
 for (BeanDefinition candidateComponent : candidateComponents) { 
  if (candidateComponent instanceof AnnotatedBeanDefinition) { //如果属于AnnotatedBeanDefinition实例类型
   // verify annotated class is an interface
   //得到@FeignClient注解的beanDefinition
   AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
   AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //获取这个bean的注解元数据
   Assert.isTrue(annotationMetadata.isInterface(),
    "@FeignClient can only be specified on an interface"); 
   //获取元数据属性
   Map<String, Object> attributes = annotationMetadata
    .getAnnotationAttributes(FeignClient.class.getCanonicalName());
   //获取@FeignClient中配置的服务名称。
   String name = getClientName(attributes);
   registerClientConfiguration(registry, name,
    attributes.get("configuration"));

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

FeignClient Bean的注册

这个方法就是把FeignClient接口注册到Spring IOC容器,

private void registerFeignClient(BeanDefinitionRegistry registry,
  AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
 String className = annotationMetadata.getClassName(); //获取FeignClient接口的类全路径
 Class clazz = ClassUtils.resolveClassName(className, null); //加载这个接口,得到Class实例
 //构建ConfigurableBeanFactory,提供BeanFactory的配置能力
 ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory 
   ? (ConfigurableBeanFactory) registry : null;
 //获取contextId
 String contextId = getContextId(beanFactory, attributes);
 String name = getName(attributes);
 //构建一个FeignClient FactoryBean,这个是工厂Bean。
 FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
 //设置工厂Bean的相关属性
 factoryBean.setBeanFactory(beanFactory);
 factoryBean.setName(name);
 factoryBean.setContextId(contextId);
 factoryBean.setType(clazz);
 
 //BeanDefinitionBuilder是用来构建BeanDefinition对象的建造器
 BeanDefinitionBuilder definition = BeanDefinitionBuilder
   .genericBeanDefinition(clazz, () -> {
    //把@FeignClient注解配置中的属性设置到FactoryBean中。
   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(); //factoryBean.getObject() ,基于工厂bean创造一个bean实例。
   });
 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //设置注入模式,采用类型注入
 definition.setLazyInit(true); //设置延迟华
 validate(attributes); 
 //从BeanDefinitionBuilder中构建一个BeanDefinition,它用来描述一个bean的实例定义。
 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); //把BeanDefinition的这个bean定义注册到IOC容器。
}

综上代码分析,其实实现逻辑很简单。

  1. 创建一个BeanDefinitionBuilder。
  2. 创建一个工厂Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean中
  3. 调用registerBeanDefinition注册到IOC容器中

关于FactoryBean

在上述的逻辑中,我们重点需要关注的是FactoryBean,这个大家可能接触得会比较少一点。

首先,需要注意的是FactoryBean和BeanFactory是不一样的,FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。

而,BeanFactory是Spring容器中的一个基本类也是很重要的一个类,在BeanFactory中可以创建和管理Spring容器中的Bean。

下面这段代码是FactoryBean接口的定义。

public interface FactoryBean<T> {
 String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

 @Nullable
 T getObject() throws Exception;

 @Nullable
 Class<?> getObjectType();

 default boolean isSingleton() {
  return true;
 }
}

从上面的代码中我们发现在FactoryBean中定义了一个Spring Bean的很重要的三个特性:是否单例、Bean类型、Bean实例,这也应该是我们关于Spring中的一个Bean最直观的感受。

FactoryBean自定义使用

下面我们来模拟一下@FeignClient解析以及工厂Bean的构建过程。

  1. 先定义一个接口,这个接口可以类比为我们上面描述的FeignClient.
public interface IHelloService {

 String say();
}

  1. 接着,定义一个工厂Bean,这个工厂Bean中主要是针对IHelloService生成动态代理。
public class DefineFactoryBean implements FactoryBean<IHelloService> {

 @Override
 public IHelloService getObject() {
  IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
    new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {
   System.out.println("begin execute");
   return "Hello FactoryBean";
  });
  return helloService;
 }

 @Override
 public Class<?> getObjectType() {
  return IHelloService.class;
 }
}
  1. 通过实现ImportBeanDefinitionRegistrar这个接口,来动态注入Bean实例
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

 @Override
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
          BeanDefinitionRegistry registry) {

  DefineFactoryBean factoryBean=new DefineFactoryBean();
  BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(
    IHelloService.class,()-> factoryBean.getObject());
  BeanDefinition beanDefinition=definition.getBeanDefinition();
  registry.registerBeanDefinition("helloService",beanDefinition);
 }
}
  1. 声明一个注解,用来表示动态bean的注入导入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. 编写测试类,测试IHelloService这个接口的动态注入
@Configuration
@EnableGpRegistrar
public class TestMain {

 public static void main(String[] args) {
  ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
  IHelloService is=applicationContext.getBean(IHelloService.class);
  System.out.println(is.say());

 }
}
  1. 运行上述的测试方法,可以看到IHelloService这个接口,被动态代理并且注入到了IOC容器。

FeignClientFactoryBean

由上述案例可知,Spring IOC容器在注入FactoryBean时,会调用FactoryBean的getObject()方法获得bean的实例。因此我们可以按照这个思路去分析FeignClientFactoryBean

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

构建对象Bean的实现代码如下,这个代码的实现较长,我们分为几个步骤来看

Feign上下文的构建

先来看上下文的构建逻辑,代码部分如下。

<T> T getTarget() {
 FeignContext context = beanFactory != null
   ? beanFactory.getBean(FeignContext.class)
   : applicationContext.getBean(FeignContext.class);
 Feign.Builder builder = feign(context);
}

两个关键的对象说明:

  1. FeignContext是全局唯一的上下文,它继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文,FeignContext注册到容器是在FeignAutoConfiguration上完成的,代码如下!
//FeignAutoConfiguration.java

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

在初始化FeignContext时,会把configurations放入到FeignContext中。configuration表示当前被扫描到的所有@FeignClient。

  1. Feign.Builder用来构建Feign对象,基于builder实现上下文信息的构建,代码如下。
protected Feign.Builder feign(FeignContext context) {
 FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
 Logger logger = loggerFactory.create(type);

 // @formatter:off
 Feign.Builder builder = get(context, Feign.Builder.class)
  // required values
  .logger(logger)
  .encoder(get(context, Encoder.class))
  .decoder(get(context, Decoder.class))
  .contract(get(context, Contract.class)); //contract协议,用来实现模版解析(后面再详细分析)
 // @formatter:on

 configureFeign(context, builder);
 applyBuildCustomizers(context, builder);

 return builder;
}

从代码中可以看到,feign方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如loggerencoderdecoder等。因此,从这个分析过程中,我们不难猜测到它的原理结构,如下图所示

父子容器隔离的实现方式如下,当调用get方法时,会从context中去获取指定type的实例对象。

//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
 T instance = context.getInstance(contextId, type);
 if (instance == null) {
  throw new IllegalStateException(
   "No bean found of type " + type + " for " + contextId);
 }
 return instance;
}

接着,调用NamedContextFactory中的getInstance方法。

//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
 //根据`name`获取容器上下文
 AnnotationConfigApplicationContext context = this.getContext(name);

 try {
  //再从容器上下文中获取指定类型的bean。
  return context.getBean(type);
 } catch (NoSuchBeanDefinitionException var5) {
  return null;
 }
}

getContext方法根据namecontexts容器中获得上下文对象,如果没有,则调用createContext创建。

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

 return (AnnotationConfigApplicationContext)this.contexts.get(name);
}

生成动态代理

第二个部分,如果@FeignClient注解中,没有配置url,也就是不走绝对请求路径,则执行下面这段逻辑。

由于我们在@FeignClient注解中使用的是name,所以需要执行负载均衡策略的分支逻辑。

<T> T getTarget() {
 //省略.....
  if (!StringUtils.hasText(url)) { //是@FeignClient中的一个属性,可以定义请求的绝对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));
 }
 //省略....
}

loadBalance方法的代码实现如下,其中Client是Spring Boot自动装配的时候实现的,如果替换了其他的http协议框架,则client则对应为配置的协议api。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
       HardCodedTarget<T> target) {
 //Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
 Client client = getOptional(context, Client.class);
 if (client != null) {
  builder.client(client); //针对当前Feign客户端,设置网络通信的client
  //targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
  Targeter targeter = get(context, Targeter.class);
  return targeter.target(this, builder, context, target);
 }

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

HystrixTarget.target代码如下,我们没有集成Hystrix,因此不会触发Hystrix相关的处理逻辑。

//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
     FeignContext context, Target.HardCodedTarget<T> target) {
 if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //没有配置Hystrix,则走这部分逻辑
  return feign.target(target);
 }
 //省略....

 return feign.target(target);
}

进入到Feign.target方法,代码如下。

//Feign.java
public <T> T target(Target<T> target) {
 return this.build().newInstance(target); //target.HardCodedTarget
}

public Feign build() {
 //这里会构建一个LoadBalanceClient
 Client client = (Client)Capability.enrich(this.client, this.capabilities);
 Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
 List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
  return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
 }).collect(Collectors.toList());
 //OpenFeign Log配置
 Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
 //模版解析协议(这个接口非常重要:它决定了哪些注解可以标注在接口/接口方法上是有效的,并且提取出有效的信息,组装成为MethodMetadata元信息。)
 Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
 //封装Request请求的 连接超时=默认10s ,读取超时=默认60
 Options options = (Options)Capability.enrich(this.options, this.capabilities);
 //编码器
 Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
 //解码器
 Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
 
 InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
 QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
 //synchronousMethodHandlerFactory, 同步方法调用处理器(很重要,后续要用到)
 Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
 //ParseHandlersByName的作用就是我们传入Target(封装了我们的模拟接口,要访问的域名),返回这个接口下的各个方法,对应的执行HTTP请求需要的一系列信息
 ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
 
 return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

build方法中,返回了一个ReflectiveFeign的实例对象,先来看ReflectiveFeign中的newInstance方法。

public <T> T newInstance(Target<T> target) { //修饰了@FeignClient注解的接口方法封装成方法处理器,把指定的target进行解析,得到需要处理的方法集合。 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //定义一个用来保存需要处理的方法的集合 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //JDK8以后,接口允许默认方法实现,这里是对默认方法进行封装处理。 List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();	 //遍历@FeignClient接口的所有方法 for (Method method : target.type().getMethods()) {  //如果是Object中的方法,则直接跳过  if (method.getDeclaringClass() == Object.class) {   continue;     } else if (Util.isDefault(method)) {//如果是默认方法,则把该方法绑定一个DefaultMethodHandler。   DefaultMethodHandler handler = new DefaultMethodHandler(method);   defaultMethodHandlers.add(handler);   methodToHandler.put(method, handler);  } else {//否则,添加MethodHandler(SynchronousMethodHandler)。   methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));  } } //创建动态代理类。 InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),           new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {  defaultMethodHandler.bindTo(proxy); } return proxy;}

上述代码,其实也不难理解。

    1. 默认方法,绑定DefaultMethodHandler
    2. 远程方法,绑定SynchronousMethodHandler

MethodHandler,会把方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储,如下图所示。

FeignClient接口解析

接口解析也是Feign很重要的一个逻辑,它能把接口声明的属性转化为HTTP通信的协议参数。

public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //here}

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

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) {   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;}

为了更好的理解上述逻辑,我们可以借助下面这个图来理解!

阶段性小结

通过上述过程分析,被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler。

当触发方法调用时,会被FeignInvocationHandler#invoke拦截,FeignClientFactoryBean在实例化过程中所做的事情如下图所示。

总结来说就几个点:

  1. 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
  2. Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
  3. 对目标服务,使用LoadBalance以及Hystrix进行包装
  4. 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
  5. 遍历MethodHandler列表,针对需要远程通信的方法,设置SynchronousMethodHandler处理器,用来实现同步远程调用。
  6. 使用JDK中的动态代理机制构建动态代理对象。

远程通信实现

在Spring启动过程中,把一切的准备工作准备就绪后,就开始执行远程调用。

在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。

那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。

//FeignInvocationHandler.java@Overridepublic 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);}

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

this.dispatch,其实就是在初始化过程中创建的,private final Map<Method, MethodHandler> dispatch;实例。

SynchronousMethodHandler.invoke

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

@Override
public Object invoke(Object[] argv) throws Throwable { //argv,表示调用方法传递的参数。  
 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; 
 } 
 }
}

上述代码的执行流程中,需要先构造一个Request对象,然后调用client.execute方法执行网络通信请求,整体实现流程如下。

executeAndDecode

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

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
 Request request = targetRequest(template); //把template转化为请求报文 
 if (logLevel != Logger.Level.NONE) { //设置日志level  
 logger.logRequest(metadata.configKey(), logLevel, request); 
 } 
 Response response; 
 long start = System.nanoTime(); 
 try {  
 //发起请求,此时client是LoadBalanceFeignClient,需要先对服务名称进行解析负载 
 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; 
 } 
}

LoadBalanceClient

@Overridepublic
Response execute(Request request, Request.Options options) throws IOException { 
 try { 
 URI asUri = URI.create(request.url()); //获取请求uri,此时的地址还未被解析。 
 String clientName = asUri.getHost(); //获取host,实际上就是服务名称 
 URI uriWithoutHost = cleanUrl(request.url(), clientName); 
 FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);		//加载客户端的配置信息  
 IClientConfig requestConfig = getClientConfig(options, clientName);  //创建负载均衡客户端(FeignLoadBalancer),执行请求 
 return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); 
 } catch (ClientException e) {
 IOException io = findIOException(e); 
 if (io != null) {  
  throw io; 
 }  
 throw new RuntimeException(e); 
 }
}

从上面的代码可以看到,lbClient(clientName) 创建了一个负载均衡的客户端,它实际上就是生成的如下所述的类:

public class FeignLoadBalancer extends		AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {

整体总结

OpenFeign的整体通信原理解析,如下图所示。

 

小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。
我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【07】即可免费获取

 

原文出处:www.shaoqun.com/a/1500760.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值