zoukankan      html  css  js  c++  java
  • Feign源码解析系列-核心初始化

    开始

    初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么。

    内容

    从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient,都会组装一个FactoryBean即FeignClientFactoryBean注册到spring容器中,如此在spring 容器初始化的时候,创建FeignClient的Bean时都会调用FeignClientFactoryBean的getObject方法。
    FeignClientFactoryBean是Spring的FactoryBean,在Spring的世界里可以通过xml定义bean,也可以通过@Bean注解的方法组装bean,但如果我们要的bean产生过程比较复杂,使用配置或单纯的new不好解决,这时候使用FactoryBean就比较合适了,在Spring中想要找某个类型的bean时,如果是FactoryBean定义的,就会调用它的getObject获取这个bean。
    FeignClientFactoryBean的getObject方法:

    public Object getObject() throws Exception {
       FeignContext context = applicationContext.getBean(FeignContext.class);
       // 构建Feign.Builder
       Feign.Builder builder = feign(context);
       if (!StringUtils.hasText(this.url)) {
          String url;
          if (!this.name.startsWith("http")) {
             url = "http://" + this.name;
          }
          else {
             url = this.name;
          }
          url += cleanPath();
          return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                this.name, url));
       }
       if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
          this.url = "http://" + this.url;
       }
       String url = this.url + cleanPath();
       Client client = getOptional(context, Client.class);
       if (client != null) {
          if (client instanceof LoadBalancerFeignClient) {
             // not lod balancing because we have a url,
             // but ribbon is on the classpath, so unwrap
             client = ((LoadBalancerFeignClient)client).getDelegate();
          }
          builder.client(client);
       }
       Targeter targeter = get(context, Targeter.class);
       return targeter.target(this, builder, context, new HardCodedTarget<>(
             this.type, this.name, url));
    }
    

    构建feign.builder时会向FeignContext获取配置的Encoder,Decoder等各种信息。FeignContext在上篇中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器,凡是在子容器中找不到的对象,再从父容器中找。
    我们可以在Feign.Builder中看全部的可配置的属性,会发现有些信息在feignclient注解上有可以直接通过注解属性字段进行设置,比如ecode404,而有些属性是只能通过注解属性configuration配置configuration类来注入配置信息,比如:Retryer。另外除了通过在注解属性上进行配置信息外,也可以通过FeignClientProperties来配置这些信息。
    在configureFeign方法中看到可以通通过defaultToProperties属性来控制两者的优先级,默认为true,比如defaultToProperties设置为false时,则会先向Feign.Builder放配置文件配置的信息,然后再放注解上配置的,后放的当然可以覆盖先放的,所以注解配置的优先级就算高的(除了RequestInterceptor,这个是没有什么优先级的,是add上去的)。

    protected Feign.Builder feign(FeignContext context) {
       FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
       Logger logger = loggerFactory.create(this.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));
       // @formatter:on
       configureFeign(context, builder);
       return builder;
    }
    protected void configureFeign(FeignContext context, Feign.Builder builder) {
       FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
       if (properties != null) {
          if (properties.isDefaultToProperties()) {
             configureUsingConfiguration(context, builder);
             configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
             configureUsingProperties(properties.getConfig().get(this.name), builder);
          } else {
             configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
             configureUsingProperties(properties.getConfig().get(this.name), builder);
             configureUsingConfiguration(context, builder);
          }
       } else {
          configureUsingConfiguration(context, builder);
       }
    }
    protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
       Logger.Level level = getOptional(context, Logger.Level.class);
       if (level != null) {
          builder.logLevel(level);
       }
       Retryer retryer = getOptional(context, Retryer.class);
       if (retryer != null) {
          builder.retryer(retryer);
       }
       ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
       if (errorDecoder != null) {
          builder.errorDecoder(errorDecoder);
       }
       Request.Options options = getOptional(context, Request.Options.class);
       if (options != null) {
          builder.options(options);
       }
       Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
             this.name, RequestInterceptor.class);
       if (requestInterceptors != null) {
          builder.requestInterceptors(requestInterceptors.values());
       }
       if (decode404) {
          builder.decode404();
       }
    }
    

    无论是通过配置文件还是注解属性,能够控制的都是一个feignclient整体的配置。而我们在写feign接口的方法是,还需要定义这个接口方法的http描述信息,比如请求路径,请求方式,参数定义等等。也就是说,对于一个单独的请求来说,完整配置的粒度要到feign接口里的方法级别。
    在getObject方法的最后会调用Targeter.target方法来组装对象,Targeter是可以被扩展的,先不展开了,在默认的实现中会调用前面组装好的Feign.Builder的target方法:

    class DefaultTargeter implements Targeter {
       @Override
       public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                      Target.HardCodedTarget<T> target) {
          return feign.target(target);
       }
    }
    

    Feign.Builder的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);
        ParseHandlersByName handlersByName =
            new ParseHandlersByName(contract, options, encoder, decoder,
                                    errorDecoder, synchronousMethodHandlerFactory);
        return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
      }
    

    可以想象,我们只是定义了接口,通过接口的方法我们需要达成一个请求应用的操作,肯定是需要产生一个类来实现这些接口的,这里使用动态代理非常合适,那么事情就变得简单了,通过jdk自带的动态代理方式为接口产生一个代理实现类。这个实现思路可以借鉴到其他的场景,比如比较熟悉的mybatis定义的mapper接口,也是不需要实现的,实现的方式和这里是一模一样。
    这个实现从ReflectiveFeign的newInstance(target)方法开始:

    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()) {
        if (method.getDeclaringClass() == Object.class) {
          continue;
        } else if(Util.isDefault(method)) {
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {
          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;
    }
    

    从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。
    for循环是在过滤不必要的方法,有意思的一个地方:Util.isDefault(method)这个方法展开看一下:

    /**
     * Identifies a method as a default instance 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.isDefault()是因为嫌弃它不够全面的识别,说应该过滤掉合成(synthetic)方法,synthetic methods是编译时自动加入的方法。

    另外,Map<String, MethodHandler>的key是用Feign.configKey(target.type(), method)生成的,我觉得是可以通用:

    public static String configKey(Class targetType, Method method) {
      StringBuilder builder = new StringBuilder();
      builder.append(targetType.getSimpleName());
      builder.append('#').append(method.getName()).append('(');
      for (Type param : method.getGenericParameterTypes()) {
        param = Types.resolve(targetType, targetType, param);
        builder.append(Types.getRawType(param).getSimpleName()).append(',');
      }
      if (method.getParameterTypes().length > 0) {
        builder.deleteCharAt(builder.length() - 1);
      }
      return builder.append(')').toString();
    }
    

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

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

    当代理类接到执行请求时, 通过一个map分发给对应的MethodHandler执行,如此就实现了针对每个方法的个性化代理实现。
    所以,结构就是一个InvocationHandler对应多个MethodHandler:

    MethodHandler的实现这里是使用SynchronousMethodHandler,它实现的invoke方法如下:

    public Object invoke(Object[] argv) throws Throwable {
      RequestTemplate template = buildTemplateFromArgs.create(argv);
      Retryer retryer = this.retryer.clone();
      while (true) {
        try {
          return executeAndDecode(template);
        } catch (RetryableException e) {
          retryer.continueOrPropagate(e);
          if (logLevel != Logger.Level.NONE) {
            logger.logRetry(metadata.configKey(), logLevel);
          }
          continue;
        }
      }
    }
    

    到这里就会创建http请求模版,这部分后续再深入。

    结束

    可以看到产生的FeignClient的代理对象,代理了接口方法,实际会生成一个http请求模版,进行请求操作。
    回到前面触发的地方是spring调用FeignClientFactoryBean的getObject方法,所以产生的这个FeignClient的代理对象会在spring容器中,我们直接可以从spring容器中拿来使用。

  • 相关阅读:
    luogu P3368 【模板】树状数组 2
    dp
    vijos 羽毛
    luogu tyvj 纪念品分组
    codevs 1259 最大正方形子矩阵 WD
    python 序列化之pickle模块 json模块
    python 类的进阶
    python 面向对象与类的基本知识
    python 异常处理
    python time模块 sys模块 collections模块 random模块 os模块 序列化 datetime模块
  • 原文地址:https://www.cnblogs.com/killbug/p/10562164.html
Copyright © 2011-2022 走看看