Springboot Feign整合源码解析

一、简述

Springboot Feign整合源码解析_第1张图片
图片

从Spring官网的start示例可以看出,feign的使用仅仅是声明一个接口,然后使用spring mvc或者 JAX-RS的方式给方法打上注解,调用方就可以像使用本地Service那样,依赖并调用了。

了解Spring的同学应该都知道,定义一个接口,没有实现类,想要把这个接口直接注入到Spring容器是不可能的,肯定是用了动态代理生成代理类,并且改变了Spring IOC的行为,将Bean的实例用动态代理类填充了。

那么接下来,我们就从入口,通过源码一步步来解析Feign和Springboot是怎么整合在一起的。

二、FeignClient是怎么实例化到Spring容器的

1、EnableFeignClients 注解

如果我们要开启Feign接口的扫描,会在我们项目启动类上,加上一个@EnableFeignClients的注解,这个大家都会用,但是为什么加了注解,Spring就会去扫描呢?

EnableFeignClients注解内容:

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

这个注解还有一个Import注解,我们先看Import注解的说明:

/**
 * Indicates one or more {@link Configuration @Configuration} classes to import.
 *
 * 

Provides functionality equivalent to the {@code } element in Spring XML. * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}). * *

{@code @Bean} definitions declared in imported {@code @Configuration} classes should be * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired} * injection. Either the bean itself can be autowired, or the configuration class instance * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly * navigation between {@code @Configuration} class methods. * *

May be declared at the class level or as a meta-annotation. * *

If XML or other non-{@code @Configuration} bean definition resources need to be * imported, use the {@link ImportResource @ImportResource} annotation instead. * * @author Chris Beams * @author Juergen Hoeller * @since 3.0 * @see Configuration * @see ImportSelector * @see ImportResource */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import {

它注释其中有一句:

Allows for importing {@code @Configuration} classes, {@link ImportSelector} and {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).

说明可以通过@Import注解里,传入ImportSelector.class和ImportBeanDefinitionRegistrar.class的实现类,实现Bean的注册。
如何证明?
在SpringBoot启动过程中,实际上调用了Spring的核心方法AbstractApplicationContext#refresh()。

Springboot Feign整合源码解析_第2张图片
image

在refresh方法中,Spring在初始化Bean工厂的时候,会执行ConfigurationClassPostProcessor这个后置处理器,在这个后置处理器中,就会执行ImportSelector或者ImportBeanDefinitionRegistrar实现类的接口实现方法,生成BeanDefinition,后续会被Spring实例化。而且基本我们用到的组件@Enablexxxx,都是应用这个原理,把一个组件的初始化类用@Import来给注解打tag,再将初始化类实现ImportSelector或者ImportBeanDefinitionRegistrar作为value设置到注解中,让Spring自己去调用实例方法,这样就可以用一个自定义注解@Enablexxxx一键开启或者关闭某个组件了,理解了这个,我们应该知道给SpringBoot写一个小组件应该怎么开始了。

这一块的源码不做细讲,因为refresh方法太复杂了,篇幅有限,有兴趣的可以调试 ConfigurationClassPostProcessor这个后置处理器postProcessBeanFactory方法。

2、FeignClientsRegistrar

我们回到ImportBeanDefinitionRegistrar的实现类FeignClientsRegistrar:

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

在registerFeignClients方法中:
首先确定哪些类需要被扫描

Springboot Feign整合源码解析_第3张图片
image.png

如果@EnableFeignClients 中的clients属性配置了有值,其它的属性的配置就不会生效。

然后循环这些包路径,找到对应的有@FeignClient标注的类,执行registerFeignClient方法:

Springboot Feign整合源码解析_第4张图片
image
Springboot Feign整合源码解析_第5张图片
图片

这里最重要的就是:

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

3、FeignClientFactoryBean

FactoryBean是一个接口,其中有一个getObject方法,在执行AbstractBeanFactory#getBean方法时,如果你的name不加上"&"前缀的话,会得到这个getObject方法返回的对象实例。所以我们如果要找Feign的实例,就要去看FeignClientFactoryBean 这个类的getObject方法:

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

在getTarget()方法中:

Springboot Feign整合源码解析_第6张图片
image

会判断@FeignClient注解的url属性有没有值,我们一般是写服务名,这样就会最终生成”http://service-name“的url,然后再执行loadBalance方法。

protected  T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget target) {
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
      Targeter targeter = get(context, Targeter.class);
      return targeter.target(this, builder, context, target);
   }

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

这里有两个比较看不懂的变量,Client client和Targeter targeter,client变量主要用于设置请求客户端对象,其中客户端的负载均衡就是在这里初始化的,这个留在下一节单独讲。target是用于生成代理对象,如果这个Feign有配置熔断器,也会在这里初始化熔断器相关信息。

4、Targeter,Springboot自动装配

但是Targeter接口有两个实现类,那么在这里,会返回哪一个呢?


Springboot Feign整合源码解析_第7张图片
图片

这个就要回到我们Springboot的自动装配知识点了。

Springboot Feign整合源码解析_第8张图片
图片

我们知道,在Springboot启动的过程中,会调用AbstractApplicationContext#refresh()方法,也知道@Import直接的作用,在我们Springboot启动类中,会定义一个注解: @SpringBootApplication,这个注解上面再有@EnableAutoConfiguration注解,@EnableAutoConfiguration注解上面有一个@Import(AutoConfigurationImportSelector.class),

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AnnotationAttributes attributes =getAttributes(annotationMetadata);
   //在这里扫描classpath下所有 META-INF/spring.factories 中的
  //org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置,
  //并让Spring实例化其配置的所有类
   List configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
   configurations = removeDuplicates(configurations);
   Set exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return StringUtils.toStringArray(configurations);
}

从上面的代码注释可以看出,这个类会扫描classpath下所有 META-INF/spring.factories 中的org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置,并让Spring实例化其配置的所有类。所以org.springframework.cloud.openfeign.FeignAutoConfiguration在Spring容器启动的时候就会被加载到容器并实例化。

Springboot Feign整合源码解析_第9张图片
image

使用哪个Targeter,取决于feign.hystrix.HystrixFeign这个类在不在classpath中。

我的工程是引入了这个jar:io.github.openfeign,所以就会使用HystrixTargeter。

进入HystrixTargeter#target方法:

Springboot Feign整合源码解析_第10张图片
图片

在这个方法里,首先判断@FeignClient注解有没有配置fallback属性,再判断fallbackFactory属性,如果都没有配置,就直接调用feign.target(target)。这里我们假设都没有配置,进入Feign.target()方法:

public  T target(Target target) {
  return build().newInstance(target);
}

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

@Override
public  T newInstance(Target target) {
//
  Map nameToHandler = targetToHandlersByName.apply(target);
  Map methodToHandler = new LinkedHashMap();
  List defaultMethodHandlers = new LinkedList();
  // 将@FeignClient接口中所有的方法存在methodToHandler这个Map中
  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if(Util.isDefault(method)) {
      //Object默认的方法,会用默认的DefaultMethodHandler
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {
      //这里通过看上面的build方法,可以知道,value是SynchronousMethodHandler
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
  //这里如果没有配置fallBack或者fallBackFactory,会返FeignInvocationHandler
  InvocationHandler handler = factory.create(target, methodToHandler);
  //对被@FeignClient注解的类利用JDK动态代理产生代理对象实例,并使用FeignInvocationHandler对这个对象进行增强
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);

  for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

5、FeignInvocationHandler

上面的代码描述了产生代理类的过程,当一个Feign接口被调用时,接下来我们就要看FeignInvocationHandler的invoke方法了:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if ("equals".equals(method.getName())) {
    try {
      Object
          otherHandler =
          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }
  //这里的dispatch就是我们定义的methodToHandler这个map,直接拿出SynchronousMethodHandler执行invoke方法
  return dispatch.get(method).invoke(args);
}

SynchronousMethodHandler#invoke

@Override
public Object invoke(Object[] argv) throws Throwable {
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {  
      //这里就是真正执行http请求和负载均衡的地方了
      return executeAndDecode(template);
    } catch (RetryableException e) {
      retryer.continueOrPropagate(e);
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
      continue;
    }
  }
}

到此为止,@FeignClient注解的类,怎么会被实例化,并且注入到Spring容器中,已经完成。

你可能感兴趣的:(Springboot Feign整合源码解析)