美文网首页
Spring Cloud Feign 源码分析 - FeignC

Spring Cloud Feign 源码分析 - FeignC

作者: 想起个帅气的头像 | 来源:发表于2020-11-02 22:57 被阅读0次

    一. 前言

    关于Feign的启动原理分析,参照另一篇Spring Cloud Feign 源码分析 - feign启动原理

    二. 源码分析

    书接上文,上篇最后提到所有带@FeignClient注解的interface都被封装成FeignClientFactoryBean的BeanDefinition。从名字上可以得知这个类是一个FactoryBean。关于FactoryBean的介绍参考...
    因此直接找getObject()。

    @Override
        public Object getObject() throws Exception {
            return getTarget();
        }
    
    /**
         * @param <T> the target type of the Feign client
         * @return a {@link Feign} client created with the specified data and the context information
         */
        <T> T getTarget() {
            FeignContext context = applicationContext.getBean(FeignContext.class);
            Feign.Builder builder = feign(context);
    
            if (!StringUtils.hasText(this.url)) {
                if (!this.name.startsWith("http")) {
                    url = "http://" + this.name;
                }
                else {
                    url = this.name;
                }
                url += cleanPath();
                return (T) 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 load 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(
                    this.type, this.name, url));
        }
    

    getTarget方法首先获取FeignContext的对象,基于这个context对当前feign的配置信息存放到Builder中。

      FeignContext context = applicationContext.getBean(FeignContext.class);
    

    首先实例化bean:FeignContext
    FeignContext的定义在FeignAutoConfiguration

    @Configuration
    @ConditionalOnClass(Feign.class)
    @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
    public class FeignAutoConfiguration {
    
        @Autowired(required = false)
        private List<FeignClientSpecification> configurations = new ArrayList<>();
    
        @Bean
        public HasFeatures feignFeature() {
            return HasFeatures.namedFeature("Feign", Feign.class);
        }
    
        @Bean
        public FeignContext feignContext() {
            FeignContext context = new FeignContext();
            context.setConfigurations(this.configurations);
            return context;
        }
    

    第一次除了创建新的FeignContext对象之外,还设置了一组configurations,
    这组configurations是FeignClientSpecification类型,通过autowired注入。
    在扫描EnableFeignClients和各个FeignClient时,将configuration对应的class封装成了FeignClientSpecification的BeanDefinition,这里从容器中取出来创建对象注入到configurations


    image.png image.png

    通过断点可以看到这里有15个FeignClientSpecification的对象


    image.png

    一个是default.开头的在启动类里配置的configuration,剩下的都是FeignClient的configuration。

    public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
    
        public FeignContext() {
            super(FeignClientsConfiguration.class, "feign", "feign.client.name");
        }
    }
    

    FeignContext继承了NamedContextFactory,对应的范型就是FeignClientSpecification,看下NamedContextFactory构造方法

    public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
            implements DisposableBean, ApplicationContextAware {
    
        public interface Specification {
            String getName();
    
            Class<?>[] getConfiguration();
        }
    
        private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    
        private Map<String, C> configurations = new ConcurrentHashMap<>();
    
        private ApplicationContext parent;
    
        private Class<?> defaultConfigType;
        private final String propertySourceName;
        private final String propertyName;
    
        public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
                String propertyName) {
            this.defaultConfigType = defaultConfigType;
            this.propertySourceName = propertySourceName;
            this.propertyName = propertyName;
        }
    
    

    这里设置了默认的defaultConfigType,feign里用的是FeignClientsConfiguration,定义了一系列的默认值。

        //分析这一句
        Feign.Builder builder = feign(context);
    
        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 <T> T get(FeignContext context, Class<T> type) {
            T instance = context.getInstance(this.contextId, type);
            if (instance == null) {
                throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
            }
            return instance;
        }
    

    在获取到FeignContext之后,开始封装Feign.Builder。
    首先通过context实例化FeignLoggerFactory的对象,因为context是NamedContextFactory的子类,会给每个contextId创建一个独立的AnnotationConfigApplicationContext上下文,每一个k-v会存储在FeignContext的全局context中,key就是contextId

    public <T> T getInstance(String name, Class<T> type) {
            AnnotationConfigApplicationContext context = getContext(name);
            if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                    type).length > 0) {
                return context.getBean(type);
            }
            return null;
        }
    
    protected AnnotationConfigApplicationContext getContext(String name) {
            if (!this.contexts.containsKey(name)) {
                synchronized (this.contexts) {
                    if (!this.contexts.containsKey(name)) {
                        this.contexts.put(name, createContext(name));
                    }
                }
            }
            return this.contexts.get(name);
        }
    
    protected AnnotationConfigApplicationContext createContext(String name) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            //1 给FeignClient设置独立的configuration
            if (this.configurations.containsKey(name)) {
                for (Class<?> configuration : this.configurations.get(name)
                        .getConfiguration()) {
                    context.register(configuration);
                }
            }
            //2 给FeignClient设置全局defaultConfiguration
            for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
                if (entry.getKey().startsWith("default.")) {
                    for (Class<?> configuration : entry.getValue().getConfiguration()) {
                        context.register(configuration);
                    }
                }
            }
            context.register(PropertyPlaceholderAutoConfiguration.class,
                    this.defaultConfigType);
            context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                    this.propertySourceName,
                    Collections.<String, Object> singletonMap(this.propertyName, name)));
            if (this.parent != null) {
                // Uses Environment from parent as well as beans
                //给子上下文设置parentContext
                context.setParent(this.parent);
            }
            context.setDisplayName(generateDisplayName(name));
            //子上下文调用refresh方法,刷新操作
            context.refresh();
            return context;
        }
    

    这三个方法的实现完全体现了NamedContextFactory的作用:
    给每个name创建一个单独的ApplicationContext子上下文对象,后续凡是这个name的ioc操作,都由独立的ApplicationContext来完成,name之间的context相互隔离。所有的子上下文保存在了Map<String, AnnotationConfigApplicationContext> contexts中。

    在创建Context时,补充了configuration的设置:
    首先(1的位置),从全局的configurations查找是否定义了只对当前name生效的configuration,也就是判断在当前name所属的FeignClient注解上是否定义了configuration。如果定义过,将这个configuration的Class封装成BeanDefinition注册到本name的子上下文中。

    接着(2的位置),从全局的configurations查找是否定义了全局配置,也就是@EnableFeignClients的defaultConfiguration的值,这里固定前缀是default.。
    如果也存在,就也将这个defaultConfiguration的Class封装成BeanDefinition注册到本name的子上下文中。

    第一次调用完毕get方法后,给每个FeignClient创建的FeignContext就完成了configuration初始化的动作,后面的所有操作,如配置encoder、decoder都是给当前的子上下文内注册BeanDefinition。最后将所有配置封装成Builder返回。

    三. 请求调度

    在getTarget()构造完成builder属性之后,开始了整个请求调度过程。

    先看第一段:

            if (!StringUtils.hasText(this.url)) {
                if (!this.name.startsWith("http")) {
                    url = "http://" + this.name;
                }
                else {
                    url = this.name;
                }
                url += cleanPath();
                return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                        this.name, url));
            }
    

    如果没有url属性,就用name来处理,把http:// + name + path 拼装成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的bean对象,默认返回LoadBalancerFeignClient的实例。

    @Configuration
    class DefaultFeignLoadBalancedConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                                  SpringClientFactory clientFactory) {
            return new LoadBalancerFeignClient(new Client.Default(null, null),
                    cachingFactory, clientFactory);
        }
    }
    
    public class LoadBalancerFeignClient implements Client {
    
        //...
        private final Client delegate;
    
        public LoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory) {
            this.delegate = delegate;
            this.lbClientFactory = lbClientFactory;
            this.clientFactory = clientFactory;
        }
    

    从LoadBalancerFeignClient的构造方法可以看到,这里使用了delegate的设计模式来代理Client.Default,扩展execute的实现。

    然后则继续实例化Targeter的bean。默认有两种实现类。

    
        @Configuration
        @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
        protected static class HystrixFeignTargeterConfiguration {
            @Bean
            @ConditionalOnMissingBean
            public Targeter feignTargeter() {
                return new HystrixTargeter();
            }
        }
    
        @Configuration
        @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
        protected static class DefaultFeignTargeterConfiguration {
            @Bean
            @ConditionalOnMissingBean
            public Targeter feignTargeter() {
                return new DefaultTargeter();
            }
        }
    

    我这里返回HystrixTargeter。调用target方法。

    @Override
        public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                            Target.HardCodedTarget<T> target) {
            if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
                return feign.target(target);
            }
            feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
            SetterFactory setterFactory = getOptional(factory.getName(), context,
                SetterFactory.class);
            if (setterFactory != null) {
                builder.setterFactory(setterFactory);
            }
            Class<?> fallback = factory.getFallback();
            if (fallback != void.class) {
                return targetWithFallback(factory.getName(), context, target, builder, fallback);
            }
            Class<?> fallbackFactory = factory.getFallbackFactory();
            if (fallbackFactory != void.class) {
                return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
            }
    
            return feign.target(target);
        }
    

    这里重点看下feign.target(target)的实现。

    public abstract class Feign {
    
    //...
        
        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, propagationPolicy);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                  errorDecoder, synchronousMethodHandlerFactory);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
      }
    }
    

    可以看到,通过build()构造了一个ReflectiveFeign的对象,将一系列feign的参数封装成了SynchronousMethodHandler和ParseHandlersByName。封装的这两个对象都是为了给后面newInstance用的。

    newInstance返回了扩展后的Targeter的代理类。


    image.png

    下面介绍下newInstance的详细过程。

     @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>();
    
        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;
      }
    
    public Map<String, MethodHandler> apply(Target key) {
          List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.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);
            } else if (md.bodyIndex() != null) {
              buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
            } else {
              buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
            }
            result.put(md.configKey(),
                factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
          }
          return result;
        }
      }
    

    apply方法就是给feignClient的每个方法都封装了一个SynchronousMethodHandler,
    factory.create(...)就是为了根据当前方法的各个参数+new SynchronousMethodHandler.Factory定义的默认参数来构造SynchronousMethodHandler
    key对应的是类名#方法名,如:MasterClientLocal#getPersons()。
    for循环则是为了封装methodToHandler,k-v分别是reflect的Method和SynchronousMethodHandler。遍历完成后,构建一个InvocationHandler的实现类:FeignInvocationHandler

     @Override
        public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
          return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
        }
      }
    
    static class FeignInvocationHandler implements InvocationHandler {
    
        private final Target target;
        private final Map<Method, MethodHandler> dispatch;
    
        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
          this.target = checkNotNull(target, "target");
          this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }
    
        @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);
        }
    

    通过传入target和dispatch,其实本质就是在调用SynchronousMethodHandler的invoke方法。而invoke方法则是扩展了http的调用动作,包括请求重试,decode处理,decode404判断等。

    @Override
      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) {
               //...
              continue;
          }
        }
      }
    
      Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = targetRequest(template);
        Response response;
        long start = System.nanoTime();
        try {
          response = client.execute(request, options);
        } catch (IOException e) {
          //...
        }
        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    
        boolean shouldClose = true;
        try {
          if (Response.class == metadata.returnType()) {
            if (response.body() == null) {
              return response;
            }
            if (response.body().length() == null ||
                response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
              shouldClose = false;
              return response;
            }
            // Ensure the response body is disconnected
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            return response.toBuilder().body(bodyData).build();
          }
          if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
              return null;
            } else {
              Object result = decode(response);
              shouldClose = closeAfterDecode;
              return result;
            }
          } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            Object result = decode(response);
            shouldClose = closeAfterDecode;
            return result;
          } else {
            throw errorDecoder.decode(metadata.configKey(), response);
          }
        } catch (IOException e) {
          //...
        } finally {
          if (shouldClose) {
            ensureClosed(response.body());
          }
        }
      }
    

    最重要的执行则是client.execute(...);
    client有两种实现类:


    image.png

    Default是带url的execute的实现,封装了最普通的http调用。
    LoadBalanceFeignClient是eureka的实现,通过获取server列表来实现loadBalance。
    也就是最开始getTarget() 方法的两段不同的实现过程的最本质区别。

    至此,FeignClientFactoryBean的源码分析告一段落。

    四. 总结

    1. getTarget的最终目的是给每个feignClient的方法封装一个HardCodedTarget的代理对象。代理的目的是实现通用扩展(重试、decode、decode404等)和loadbalance扩展
    2. 区别在于feign的注解里是否有url的属性
    3. 如果有则执行的是Default的实现类,封装普通的http调用,
    4. 如果没有url则执行LoadBalanceFeignClient的execute方法,包装了一层获取server列表来实现负载均衡的功能。
    5. 这里的扩展方式使用的是delegate的设计模式,如果想继续扩展,依然可以沿用这种方式。

    本人通过delegate方式在此基础上实现了traceId的跨feign传递。将在下一篇文章中做具体说明。

    相关文章

      网友评论

          本文标题:Spring Cloud Feign 源码分析 - FeignC

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