SpringCloud-源码分析 Feign

本文作者:陈刚,叩丁狼高级讲师。原创文章,转载请注明出处。

回顾

我们还是在之前Feign的使用案例集成上来分析Feign源码,在分析 之前我们先简单来回顾一下Feign的用法,要用Feign首选是需要在配置类似开启Feign客户端支持

@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

然后我们需要去定义Feign客户端接口

//PRODUCER:指向要访问的服务
//configuration = FooConfiguration.class  Feign Client配置类,指定负载均衡策略

//fallback = MyFeignClientImpl.class: MyFeignClient结构的实现类,需要复写 provide方法,并实现错误处理逻辑
//当访问出现故障,程序会自动调用实现类的 provide方法的错误处理逻辑。
@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class)
public interface MyFeignClient {


    //当此方法别调用会自动请求到 PRODUCER服务的 /provide 资源
    @RequestMapping(value = "/provide")
    public String provide(@RequestParam("name") String name);

}

在使用的时候就只需要调用 MyFeignClient 接口即可,SpringCloud会自动去调用对应的目标服务。

分析Feign我们还是从他的@EnableFeignClients标签开始,我们先看下他的源码

  /**
 * Scans for interfaces that declare they are feign clients (via {@link FeignClient
 * <code>@FeignClient</code>}). Configures component scanning directives for use with
 * {@link org.springframework.context.annotation.Configuration
 * <code>@Configuration</code>} classes.
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @since 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

该标签的注释告诉我们,它开启了对打了 @FeignClient 标签所在的接口的扫描,和对期配置组件的扫描支持 ,并且该标签import了一个FeignClientsRegistrar类,我们跟踪进去看一下


/**
 * @author Spencer Gibb
 * @author Jakub Narloch
 * @author Venil Noronha
 * @author Gang Li
 */
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, EnvironmentAware {

    // patterned after Spring Integration IntegrationComponentScanRegistrar
    // and RibbonClientsConfigurationRegistgrar

    private ResourceLoader resourceLoader;

    private Environment environment;

    public FeignClientsRegistrar() {

他实现了 ImportBeanDefinitionRegistrar接口,我们继续看ImportBeanDefinitionRegistrar的源码

/**
 * Interface to be implemented by types that register additional bean definitions when
 * processing @{@link Configuration} classes. Useful when operating at the bean definition
 * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
 *
 * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
 * may be provided to the @{@link Import} annotation (or may also be returned from an
 * {@code ImportSelector}).
 *
 * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #registerBeanDefinitions}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
 * </ul>
 *
 * <p>See implementations and associated unit tests for usage examples.
 *
 * @author Chris Beams
 * @since 3.1
 * @see Import
 * @see ImportSelector
 * @see Configuration
 */
public interface ImportBeanDefinitionRegistrar {

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

翻译“Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes.”得知,该接口提供了注册bean的功能支持,而registerBeanDefinitions则是bean注册的方法。 AnnotationMetadata是bean的元注解对象 ,BeanDefinitionRegistry是当前注册的bean的注册表,
我们继续跟踪一下 FeignClientsRegistrar#registerBeanDefinitions 方法

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

registerDefaultConfiguration(metadata, registry);注册默认配置,我们跟踪一下源码

private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
                      //name以  "default."+类名
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

这里获取了主程序配置类的@EnableFeignClients注解中的feign默认配置 ,然后用 “default.” 开头加上类名作为名字来注册 ,继续跟踪

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

这里把 configuration 配置交给了 BeanDefinitionBuilder然后去创建了一个 FeignClientSpecification 对象出来 ,然后调用BeanDefinitionRegistry的registerBeanDefinition方法把 包装了配置类的FeignClientSpecification对象进行注册,注册到哪儿去了呢?最终会调用BeanDefinitionRegistryd的子类DefaultListableBeanFactory#registerBeanDefinition方法注册

...省略代码...
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

@Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
  ...省略代码...
    this.beanDefinitionMap.put(beanName, beanDefinition);

这里最终会put到一个线程安全的map:beanDefinitionMap = new ConcurrentHashMap<>(256); 中,这个东西就是ico容器o( ̄︶ ̄)o 。

回过头来我们继续看 FeignClientsRegistrar#registerBeanDefinitions 方法

@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //注册默认配置到ioc容器中
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

到这里我们已经知道 registerDefaultConfiguration(metadata, registry);注册默认配置,那么 registerFeignClients(metadata, registry);方法看名字应该是注册所有的feignClient的bean,我们一步一步进去看

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
//得到组件扫描器,resourceLoader是资源加载器,
//比如加载spring配置文件就会用到ResourceLoader
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set<String> basePackages;
  //获取元注解上EnableFeignClients的属性配置
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            scanner.addIncludeFilter(annotationTypeFilter);
                      //获取元注解所在的主程序配置类的包名即项目所在的包
            basePackages = getBasePackages(metadata);
        }
        else {
            final Set<String> clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class<?> clazz : clients) {
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                @Override
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(
                    new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }

        for (String basePackage : basePackages) {
                //扫描候选的组件,会把basePackage报下的bean都扫描到,然后过滤出打了 @FeignClient 标签的接口,封装成 BeanDefinition bean描述对象
            Set<BeanDefinition> candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                     //获取FeignClient接口上的的注解(我定义的FeignClient接口是MyFeignClient)
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                      //如果贴上 @FeignClient标签的不是接口,抛出异常
                    Assert.isTrue(annotationMetadata.isInterface(),
                            "@FeignClient can only be specified on an interface");
                      //获取到@FeignClient 注解上我们配置的所有属性
                    Map<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());
//获取@FeignClient标签的服务名我这指定是“PRODUCER”
//(@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class))
                    String name = getClientName(attributes);

//注册配置对象:即会获取到configuration = FooConfiguration.class然后实现注册(将bean封装成beanDefinition对象放到ioc容器中),
//而FooConfiguration中我们自己实现了负载均衡规则
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));
                      //注册 registerFeign
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }

这里面做的事情挺多的
1.通过组件扫描器ClassPathScanningCandidateComponentProvider
2.获取元注解所在配置类的包名,
3.然后通过ClassPathScanningCandidateComponentProvider扫描打了@FeignClient标签的接口 ,然后封装成 Set<BeanDefinition>
4.获取 FeignClient 接口上的 @FeignClient标签的配置类,完成注册
5.完成 FeignClient接口本身的注册

我们继续跟踪 registerFeignClient 方法

private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
//这里使用了FeignClientFactoryBean 
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
//这里把FeignClient的所有配置属性都设置给了 FeignClientFactoryBean
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      //取别名 FeignClient结尾
        String alias = name + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

        boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

        beanDefinition.setPrimary(primary);

        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
            //注册 FeignClientFactoryBean
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

这里使用 FeignClientFactoryBean来封装 FeignClient的描述信息 ,并且注册到ioc容器中 ,使用FeignClientFactoryBean 的目的是为了在spring容器刷新过程中会使用代理工厂来创建出代理类(你是否想过,我们的MyFeignClient始终是个接口,那么他是如何实现远程服务的调用的?肯定需要创建出具体的实例去处理),我们跟踪一下 FeignClientFactoryBean

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
        ApplicationContextAware {
    /***********************************
     * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition.
     ***********************************/

    private Class<?> type;

    private String name;

    private String url;

    private String path;

    private boolean decode404;

    private ApplicationContext applicationContext;

    private Class<?> fallback = void.class;
...省略代码...
    @Override
    public Object getObject() throws Exception {
        return getTarget();
    }
...省略代码...
<T> T getTarget() {
//初始化Feign上下文对象,使用的TraceFeignContext的实例
        FeignContext context = applicationContext.getBean(FeignContext.class);
//Feign.Builder用来创建feign的构建器
        Feign.Builder builder = feign(context);
            //判断 @FeignClient 是否配置url,这里的url,name等都是从@FeignClient读取过来的
        if (!StringUtils.hasText(this.url)) {
            String url;
            //拼接http协议地址
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
                  //给服务名拼接地址,
                  //如果是@FeignClient(value="PRODUCER") ,那么这里就是拼接成 http://PRODUCER
            url += cleanPath();
                    //创建负载均衡的代理类,底层使用到了jdk动态代理,见:feign.ReflectiveFeign#newInstance方法
            return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));
        }
//如果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));
    }
....省略代码..

首先FeignClientFactoryBean实现了 FactoryBean ,而FactoryBean就是spring提供给我们实例化bean的一种方式,bean的实例化过程在 getObject 方法中进行处理 ,而在这里他在getObject方法中调用getTarget()方法。
这个方法里面做了很多事情
1.首先获取FeignContext feigin上下文对象,可以把它理解为是feign的容器对象,而FeignContext使用FeignClientsConfiguration进行配置,在FeignClientsConfiguration中会把feignClient相关的配置绑定到FeignContext中

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }

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

2.Feign.Builder builder = feign(context); 获取Feign构建器
3.判断 url是否为null,如果为null则创建负载均衡的代理对象
4.如果url不为null,创建默认的代理对象

我们继续跟踪一下 feign(context)方法

protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);
    //获取Feign.Builder 
        // @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);
        }
//设置重试策略到 Feign.Builder
        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();
        }
    }

这里获取到 builder 之后又做了后续的一些设置,比如重试策略 ,错误处理,请求超时时间设定等 。
我们跟踪一下 Feign.Builder builder = get(context, Feign.Builder.class) 方法

protected <T> T get(FeignContext context, Class<T> type) {
//调用 TraceFeignContext的getInstance方法,根据name即服务的名字来创建builder
        T instance = context.getInstance(this.name, type);
        if (instance == null) {
            throw new IllegalStateException("No bean found of type " + type + " for "
                    + this.name);
        }
        return instance;
    }
...............................
public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
         //创建builder
            return context.getBean(type);
        }
        return null;
    }
...............................

    protected AnnotationConfigApplicationContext getContext(String name) {
//这里的参数name就是目标服务的名字,如果contexts中没有就创建一个放进去
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }

这里在根据被调用的目标服务的名字判断contexts中是否存在实例(Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();),如果不存在就根据名字创建一个(这里使用了AnnotationConfigApplicationContext进行封装)放入进去,其实就是在做缓存,我们跟踪一下 createContext方法看下怎么创建的


protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//加载服务对应的配置类
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
//加载主程序配置类的@EnableFeignClients注解指定的配置, 还记得最开始会扫描配置类并以“default”开头为key注册到ioc中吗?
        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
            context.setParent(this.parent);
        }
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }

我们可以看到这里每调用一个次 createContext方法就会根据服务名字去创建一个新的AnnotationConfigApplicationContext 容器对象,然后获取Feign的配置类给context绑定 ,最后会调用context.refresh();容器刷新,最后会调用 context.getBean(type); 创建出一个 FeignBuilder出来

回到我们的主线 org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget方法 ,我们看下 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));部分是如何创建负载均衡的代理类的

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
//获得FeignClient
        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 = getOptional(context, Client.class); 这里会调用TraceFeignContext#getInstance方法获取到 Client对象,而Client有三个子实现 ,当我们使用了Feign ,这里会使用LoadBalancerFeignClient ,看名字就能知道他是一个负载均衡的FeignClient客户端 ,我们跟踪一下targeter.target,这里在创建代理类

class HystrixTargeter implements Targeter {

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

这里调用到了 HystrixTargeter的target方法,调用 Feign的target方法,通过 ReflectiveFeign 工具类的 newInstance 创建实例

 public <T> T newInstance(Target<T> target) {
//target封装了FeignClient ,这里在获取FeignClient接口的方法封装为MethodHandler
    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,接收请求,转发到methodHandler
    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;
  }

这里在获取 FeignClient接口的方法,然后封装到 MethodHandler ,如果继续跟踪targetToHandlersByName.apply(target);你会看到他底层对FeignClient的元注解做了解析

   public Map<String, MethodHandler> apply(Target key) {
//解析FeignClient接口上的注解,把配置信息封装到MethodMetadata,
//并且MethodMetadata中会有一个RequestTemplate对象会封装好要请求的服务名,参数类型等信息
      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 {
//根据元注解创建出BuildTemplateByResolvingArgs对象,而这个东西其实就是RequestTemplate的工厂
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
//这里在创建 MethodHandler ,
        result.put(md.configKey(),
                   factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
      }
      return result;
    }

,获取到MethodHandler,然后根据 FeignClient 时间jdk动态代理创建出代理类
InvocationHandler handler = factory.create(target, methodToHandler);的底层会把 target和 methodToHandler封装到 FeignInvocationHandler ,而FeignInvocationHandler就是对InvocationHandler的实现,当我们在代码中调用FeignClient接口实现远程调用时,代理类就会被执行,即 FeignInvocationHandler的invoke接受到请求时就会调用 methodToHandler的方法 。源码如下

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

dispatch.get(method).invoke(args) 这里在获取要执行的方法(MethodHandler),然后invoke调用传入参数,我们继续跟踪一下

@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) {
      //重试机制继续或传播
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

可以看到这里创建了RequestTemplate请求模板 ,然后调用executeAndDecode执行请求,并且对重试机制做了处理,我们跟踪一下buildTemplateFromArgs.create(argv);

 @Override
    public RequestTemplate create(Object[] argv) {
//创建请求模板,metadata.template()获取到的就是一个RequestTemplate,只是这里面的RequestTemplate对请求服务id,参数等做了封装
      RequestTemplate mutable = new RequestTemplate(metadata.template());
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);

        mutable.insert(0, String.valueOf(argv[urlIndex]));
      }
      Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
      for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
          if (indexToExpander.containsKey(i)) {
            value = expandElements(indexToExpander.get(i), value);
          }
//这里在把请求参数放到map中
          for (String name : entry.getValue()) {
            varBuilder.put(name, value);
          }
        }
      }
//解析处理请求地址,参数,和编码等
      RequestTemplate template = resolve(argv, mutable, varBuilder);
      if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        Object value = argv[metadata.queryMapIndex()];
        Map<String, Object> queryMap = toQueryMap(value);
        template = addQueryMapQueryParameters(queryMap, template);
      }

      if (metadata.headerMapIndex() != null) {
        template = addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
      }

      return template;
    }

这里在封装RequestTemplate请求模板和处理请求地址和参数,我们继续跟踪 executeAndDecode 方法,看一下是如何执行的


 Object executeAndDecode(RequestTemplate template) throws Throwable {
//获取请求对象
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
//调用LoadBalancerFeignClient负载均衡器客户端执行请求
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).build();
    } catch (IOException e) {
...省略代码...

这里调用了 LoadBalancerFeignClient的execute方法

@Override
    public Response execute(Request request, Request.Options options) throws IOException {
        try {
//得到完整的请求地址 http://xxxx/xx?xx=xx
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
//构建  FeignLoadBalancer.RibbonRequest Feign负载均衡的请求对象
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);

//客户端配置
            IClientConfig requestConfig = getClientConfig(options, clientName);
//发送请求
            return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                    requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
    }
-------------------------------------------------
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
    }

这里调用AbstractLoadBalancerAwareClient#executeWithLoadBalancer 方法名的大致意思是 使用负载均衡的方式去执行请求 ,而call(Server server) 的Server这是进行了负载均衡选择后的调用目标服务,那么是如何进行选择的呢,我们跟踪一下 command.submit方法

com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit:
============================================================
 public Observable<T> submit(final ServerOperation<T> operation) {
        final ExecutionInfoContext context = new ExecutionInfoContext();
        
        if (listenerInvoker != null) {
            try {
                listenerInvoker.onExecutionStart();
            } catch (AbortExecutionException e) {
                return Observable.error(e);
            }
        }

        final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
        final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();

        // Use the load balancer
//选择要调用的目标服务
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    @Override
                    // Called for each server being selected
                    public Observable<T> call(Server server) {
                        context.setServer(server);
                        final ServerStats stats = loadBalancerContext.getServerStats(server);
                        

selectServer()在选择调用的服务 ,我们跟踪进去

com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer
============================================================
    private Observable<Server> selectServer() {
        return Observable.create(new OnSubscribe<Server>() {
            @Override
            public void call(Subscriber<? super Server> next) {
                try {
//从LoadBalancer中选择服务
                    Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                    next.onNext(server);
                    next.onCompleted();
                } catch (Exception e) {
                    next.onError(e);
                }
            }
        });
    }
---------------------------------------
com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer

 public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

        // Various Supported Cases
        // The loadbalancer to use and the instances it has is based on how it was registered
        // In each of these cases, the client might come in using Full Url or Partial URL
        ILoadBalancer lb = getLoadBalancer();
        if (host == null) {
            // Partial URI or no URI Case
            // well we have to just get the right instances from lb - or we fall back
            if (lb != null){
//选择服务,lb就是LoadBalancer
                Server svc = lb.chooseServer(loadBalancerKey);
                if (svc == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Load balancer does not have available server for client: "
                                    + clientName);
                }
                host = svc.getHost();
                if (host == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Invalid Server for :" + svc);
                }
                logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
                return svc;
......
============================================================
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
    ......
//选择服务
 @Override
    public Server chooseServer(Object key) {
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
          //选择服务
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
......

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
  @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
//选择服务
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
----------------------------------------
   /**                                                                                                                                                
    * Choose a server in a round robin fashion after the predicate filters a given list of servers and load balancer key.        

轮询的方式选择服务                     
    */                                                                                                                                                
   public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {                                             
       List<Server> eligible = getEligibleServers(servers, loadBalancerKey);                                                                          
       if (eligible.size() == 0) {                                                                                                                    
           return Optional.absent();                                                                                                                  
       }                                                                                                                                              
       return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));                                                                      
   }                                                                                                                                                  

看到这里你是否看到了熟悉的代码,没错我们一路跟踪最终发现这里的服务选择和上一章Robin的负载均衡选择服务的代码是一样的 ,因为 Feign默认是集成了Ribbon技术默认轮询,当然也可以添加自己的负载均衡规则

远程调用服务我们已经知道是如何进行选择的了,我们继续跟踪一下看下是怎么调用服务的,回到主线 FeignLoadBalancer. execute

----------------------------------------------------
public class FeignLoadBalancer extends
        AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
...省略代码......
@Override
    public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
            throws IOException {
        Request.Options options;
        if (configOverride != null) {
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Request.Options(
                    override.connectTimeout(this.connectTimeout),
                    override.readTimeout(this.readTimeout));
        }
        else {
            options = new Request.Options(this.connectTimeout, this.readTimeout);
        }
        Response response = request.client().execute(request.toRequest(), options);
        return new RibbonResponse(request.getUri(), response);
    }

.......方法调用链太长.省略......
 public static class Default implements Client {

    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

    /**
     * Null parameters imply platform defaults.
     */
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;
    }

    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
    }
//发送请求啦,最终调用了feign.Client.Default#convertAndSend方法使用HttpUrlConnection发送请求
    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final HttpURLConnection
          connection =
          (HttpURLConnection) new URL(request.url()).openConnection();
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
          sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
          sslCon.setHostnameVerifier(hostnameVerifier);
        }
      }
      connection.setConnectTimeout(options.connectTimeoutMillis());
      connection.setReadTimeout(options.readTimeoutMillis());
      connection.setAllowUserInteraction(false);
      connection.setInstanceFollowRedirects(options.isFollowRedirects());
      connection.setRequestMethod(request.method());

      Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
      boolean
          gzipEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
      boolean
          deflateEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

      boolean hasAcceptHeader = false;
      Integer contentLength = null;
      for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
          hasAcceptHeader = true;
        }
        for (String value : request.headers().get(field)) {
          if (field.equals(CONTENT_LENGTH)) {
            if (!gzipEncodedRequest && !deflateEncodedRequest) {
              contentLength = Integer.valueOf(value);
              connection.addRequestProperty(field, value);
            }
          } else {
            connection.addRequestProperty(field, value);
          }
        }
      }
      // Some servers choke on the default accept string.
      if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");
      }

      if (request.body() != null) {
        if (contentLength != null) {
          connection.setFixedLengthStreamingMode(contentLength);
        } else {
          connection.setChunkedStreamingMode(8196);
        }
        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
          out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
          out = new DeflaterOutputStream(out);
        }
        try {
          out.write(request.body());
        } finally {
          try {
            out.close();
          } catch (IOException suppressed) { // NOPMD
          }
        }
      }
      return connection;
    }

最终还是使用了HttpURLConnection来实现远程调用

好吧大致总结一下步骤:

  1. EnableFeignClients开启 feign功能后会调用 FeignClientsRegistrar 的registerBeanDefinitions方法来给bean的注册

  2. FeignClientsRegistrar.registerBeanDefinitions方法中会调用 FeignClientsRegistrar .registerDefaultConfiguration方法通过获取主程序配置类的原始注解扫描到Feign的配置类,并以“default”开头为key对配置类进行注册(在给 feign代理上下文对象时会用到)

3.然后会调用 FeignClientsRegistrar .registerBeanDefinitions方法中会调用 FeignClientsRegistrar. registerFeignClients方法去注册FeignClient(生成代理类时当然要知道FeignClient接口是哪个)

4.扫描并获取到FeiginClient 接口,然后获取到FeignClient的配置相关属性进行注册(这里的注册其实就是用一个map存储起来,后面拿来用)

5.使用 FeignClientFactoryBean 对FeignClient进行封装(设置FeignClient的配置属性等),然后再使用BeanDefinitionHolder进行包裹,最后交给BeanDefinitionReaderUtils.registerBeanDefinition进行注册

6.而 FeignClientFactoryBean 中的 getObject 方法根据FeignClient的配置属性中的url时候有值来决定是否创建负载均衡的FeignClient接口的代理 ,如果咩有设置url者创建负载均衡的FeignClient代理实例

7.底层使用jdk动态代理去创建代理类,底层调用是用到了LoadBalancerFeignClient负载均衡客户端,调用FeignLoadBalancer.execute ,使用Ribbon的负载均衡选择服务,最终使用到HttpURLConnection发送请求

想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/84847547