美文网首页
Springboot Feign整合源码解析

Springboot Feign整合源码解析

作者: _Mitch | 来源:发表于2020-04-23 02:12 被阅读0次

    一、简述

    图片

    从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.
     *
     * <p>Provides functionality equivalent to the {@code <import/>} 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}).
     *
     * <p>{@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.
     *
     * <p>May be declared at the class level or as a meta-annotation.
     *
     * <p>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()。

    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方法中:
    首先确定哪些类需要被扫描

    image.png

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

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

    image 图片

    这里最重要的就是:

    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()方法中:

    image

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

    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
          HardCodedTarget<T> 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的自动装配知识点了。

    图片

    我们知道,在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<String> configurations = getCandidateConfigurations(annotationMetadata,
             attributes);
       configurations = removeDuplicates(configurations);
       Set<String> 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容器启动的时候就会被加载到容器并实例化。

    image

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

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

    进入HystrixTargeter#target方法:

    图片

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

    public <T> T target(Target<T> 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> 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>();
      // 将@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整合源码解析

          本文链接:https://www.haomeiwen.com/subject/bounihtx.html