Spring Cloud Feign 分析(四)之FeignAutoConfiguration入口配置

前面几大章节我们分析和总结了Ribbon负载均衡、Hystrix熔断、Zuul网关这三大SpringBoot应用必不可少的利器,也在前面几节分析了Feign客户端的注册过程和Feign如何做到版本兼容相关的讲解,笔者想了想还是觉得少了点东西,觉得应该把Feign的整个调用链都进行总结一遍,后续我们扩展Feign时候才能游刃有余,所以本节将分析FeignAutoConfiguration入口配置,咱们分析的Feign版本为10.10.1版本,基于SpringBoot2.3.9.RELEASE!


FeignAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
        FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
    //@FeignClient注解生成的FeignClientSpecification对象
    @Autowired(required = false)
    private List configurations = new ArrayList<>();
    //注册Feign特性描述
    @Bean
    public HasFeatures feignFeature() {
        return HasFeatures.namedFeature("Feign", Feign.class);
    }
    //创建Feign上下文
    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
    ......
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
        //注册具有Hystrix熔断功能的代理类
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }
    ......
}

在FeignAutoConfiguration这个入口配置类中我们只贴非常重要的片段,List configurations这个@FeignClient注解生成的FeignClientSpecification对象可参阅Spring Cloud Feign 分析(一)之FeignClient注册过程,FeignContext与HystrixTargeter对象我们开始逐一讲解。


FeignContext

/**
* 对外提供的实例工厂类,为每个Feign客户端创建一个ApplicationContext,并从中提取所需的Bean
*/
public class FeignContext extends NamedContextFactory {

    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }
    //获取实例
    @Nullable
    public  T getInstanceWithoutAncestors(String name, Class type) {
        try {
            return BeanFactoryUtils.beanOfType(getContext(name), type);
        }
        catch (BeansException ex) {
            return null;
        }
    }
    //获取实例集合
    @Nullable
    public  Map getInstancesWithoutAncestors(String name, Class type) {
        return getContext(name).getBeansOfType(type);
    }
}

外部主要通过该工厂类获取FeignClient客户端实例,通过继承NamedContextFactory会为每一个FeignClient客户端都创建一个ApplicationContext,以便于从每个FeignClient的ApplicationContext获取指定的Bean对象,这个工厂类最主要的特征就是隔离了每个FeignClient,每个FeignClient客户端都有自己的一个ApplicationContext上下文。


NamedContextFactory

public abstract class NamedContextFactory
        implements DisposableBean, ApplicationContextAware {
    ......
    //设置@FeignClient注解生成的FeignClientSpecification对象
    public void setConfigurations(List configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }
    ......
    //获取@FeignClient对应的ApplicationContext 应用上下文
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    //如果在contexts上下文Map中找到则创建一个@FeignClient对应的应用上下文
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
    //创建FeignClient的ApplicationContext应用上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        //创建一个ApplicationContext
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        if (this.configurations.containsKey(name)) {
            //如果有配置文件则注册到当前ApplicationContext应用上下文中
            //如果@FeignClients配置了configuration则将被注册到当前ApplicationContext应用上下文中
            for (Class configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        //@EnableFeignClients的defaultConfiguration配置将会被注册到ApplicationContext中
        for (Map.Entry entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        //注册属性配置、FeignClientsConfiguration配置类
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        //添加属性
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.singletonMap(this.propertyName, name)));
        //设置父类ApplicationContext,这样可以访问父类Bean
        if (this.parent != null) {
            context.setParent(this.parent);
            context.setClassLoader(this.parent.getClassLoader());
        }
        //设置DisplayName属性,格式为:FeignContext-FeignClient客户端name/value属性
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }
    //获取实例
    public  T getInstance(String name, Class type) {
        AnnotationConfigApplicationContext context = getContext(name);
        try {
            return context.getBean(type);
        }
        catch (NoSuchBeanDefinitionException e) {
            // ignore
        }
        return null;
    }
    ......
}

通过NamedContextFactory中的代码片段以及注释信息,我们更佳直观的看出,和命名空间很像,每一个@FeignClient对应的FeignClientSpecification对象都会生成一个专属于这个@FeignClient的一个应用上下文ApplicationContext,起到了隔离作用,生成ApplicationContext应用上下文的步骤如下:

  1. 将@FeignClient注解生成的FeignClientSpecification对象添加到configurations配置Map中
  2. 根据@FeignClient的name、value属性生成一个ApplicationContext应用上下文
  3. 将@FeignClients中的configuration配置注册到当前ApplicationContext上下文中
  4. 将@EnableFeignClients的defaultConfiguration配置注册到当前上下文中
  5. 注册属性配置类、FeignClientsConfiguration客户端配置类
  6. 添加MapPropertySource属性配置
  7. 设置父类ApplicationContext,这样可以访问父类Bean
  8. 将当前创建的ApplicationContext添加到上下文contexts中,每个@FeignClient都会生成一个ApplicationContext上下文,起隔离的作用
  9. 对外提供各种getInstance获取实例方法

上面我们分析了FeignContext这个上下文,为每个@FeignClient客户端创建一个ApplicationContext,外部通过这个工厂类获取所需要的实例Bean,那下面我们继续聊聊一个非常重要的目标执行类,HystrixTargeter具备了熔断能力的执行类!

HystrixTargeter

class HystrixTargeter implements Targeter {

    @Override
    public  T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget target) {
        //是否为Feign.Builder 类型,若不是则直接创建代理对象并执行
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        //转换为HystrixFeign.Builder类型
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        //获取上下文id,其实就是获取的@FeignClient注解的name、value属性值
        String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
                : factory.getContextId();
        //获取SetterFactory,主要是HystrixCommand的groupKey、commandKey参数,默认setterFactory为空
        SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
        //setterFactory不为空就设置
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        //获取降级方法,默认为void初始状态
        Class fallback = factory.getFallback();
        if (fallback != void.class) {
            //如果有设置了fallback,则使用
            return targetWithFallback(name, context, target, builder, fallback);
        }
        //获取降级工厂类FallbackFactory,默认为void初始状态
        Class fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(name, context, target, builder,
                    fallbackFactory);
        }
        //调用HystrixFeign#build()
        return feign.target(target);
    }
    //具有FallbackFactory的目标执行类
    private  T targetWithFallbackFactory(String feignClientName, FeignContext context,
            Target.HardCodedTarget target, HystrixFeign.Builder builder,
            Class fallbackFactoryClass) {
        //获取降级工厂实例
        FallbackFactory fallbackFactory = (FallbackFactory) getFromContext(
                "fallbackFactory", feignClientName, context, fallbackFactoryClass,
                FallbackFactory.class);
        //返回具有FallbackFactory的代理实例
        return builder.target(target, fallbackFactory);
    }
    //具有Fallback的目标执行类
    private  T targetWithFallback(String feignClientName, FeignContext context,
            Target.HardCodedTarget target, HystrixFeign.Builder builder,
            Class fallback) {
        //获取降级实例
        T fallbackInstance = getFromContext("fallback", feignClientName, context,
                fallback, target.type());
        //返回具有fallback的代理实例
        return builder.target(target, fallbackInstance);
    }
    //返回指定类型的实例
    private  T getFromContext(String fallbackMechanism, String feignClientName,
            FeignContext context, Class beanType, Class targetType) {
        Object fallbackInstance = context.getInstance(feignClientName, beanType);
        if (fallbackInstance == null) {
            throw new IllegalStateException(String.format(
                    "No " + fallbackMechanism
                            + " instance of type %s found for feign client %s",
                    beanType, feignClientName));
        }

        if (!targetType.isAssignableFrom(beanType)) {
            throw new IllegalStateException(String.format("Incompatible "
                    + fallbackMechanism
                    + " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
                    beanType, targetType, feignClientName));
        }
        return (T) fallbackInstance;
    }
    //根据@FeignClient注解的name、value属性值获取对应beanType实例
    private  T getOptional(String feignClientName, FeignContext context,
            Class beanType) {
        return context.getInstance(feignClientName, beanType);
    }
}

通过对HystrixTargeter的讲解,我们看到逻辑逐渐复杂起来,比较直观的能看出这个类具备了Hystrix熔断功能,再简单点,其实我们能这样理解:HystrixTargeter会返回一个具备Hystrix熔断功能的代理对象,内部执行顺序就是请求先经由Hystrix,然后再由LoadBalancerFeignClient进行负载均衡请求最终的结果!


这里我们只是简单的总结了HystrixTargeter类的作用,feign.target(target)这一句之后的逻辑相对更佳复杂,及生成代理这个过程和代理内部又做了哪些事情,将会放在后续的文章中进行总结和讲解!

你可能感兴趣的:(Spring Cloud Feign 分析(四)之FeignAutoConfiguration入口配置)