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

前面幾大章節(jié)我們分析和總結(jié)了Ribbon負(fù)載均衡陋气、Hystrix熔斷巩趁、Zuul網(wǎng)關(guān)這三大SpringBoot應(yīng)用必不可少的利器议慰,也在前面幾節(jié)分析了Feign客戶端的注冊過程和Feign如何做到版本兼容相關(guān)的講解别凹,筆者想了想還是覺得少了點(diǎn)東西番川,覺得應(yīng)該把Feign的整個(gè)調(diào)用鏈都進(jìn)行總結(jié)一遍颁督,后續(xù)我們擴(kuò)展Feign時(shí)候才能游刃有余沉御,所以本節(jié)將分析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<FeignClientSpecification> configurations = new ArrayList<>();
    //注冊Feign特性描述
    @Bean
    public HasFeatures feignFeature() {
        return HasFeatures.namedFeature("Feign", Feign.class);
    }
    //創(chuàng)建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這個(gè)入口配置類中我們只貼非常重要的片段怀跛,List<FeignClientSpecification> configurations這個(gè)@FeignClient注解生成的FeignClientSpecification對象可參閱Spring Cloud Feign 分析(一)之FeignClient注冊過程吻谋,F(xiàn)eignContext與HystrixTargeter對象我們開始逐一講解。


FeignContext

/**
* 對外提供的實(shí)例工廠類戒祠,為每個(gè)Feign客戶端創(chuàng)建一個(gè)ApplicationContext得哆,并從中提取所需的Bean
*/
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

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

外部主要通過該工廠類獲取FeignClient客戶端實(shí)例贩据,通過繼承NamedContextFactory會(huì)為每一個(gè)FeignClient客戶端都創(chuàng)建一個(gè)ApplicationContext栋操,以便于從每個(gè)FeignClient的ApplicationContext獲取指定的Bean對象闸餐,這個(gè)工廠類最主要的特征就是隔離了每個(gè)FeignClient,每個(gè)FeignClient客戶端都有自己的一個(gè)ApplicationContext上下文矾芙。


NamedContextFactory

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {
    ......
    //設(shè)置@FeignClient注解生成的FeignClientSpecification對象
    public void setConfigurations(List<C> configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }
    ......
    //獲取@FeignClient對應(yīng)的ApplicationContext 應(yīng)用上下文
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    //如果在contexts上下文Map中找到則創(chuàng)建一個(gè)@FeignClient對應(yīng)的應(yīng)用上下文
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
    //創(chuàng)建FeignClient的ApplicationContext應(yīng)用上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        //創(chuàng)建一個(gè)ApplicationContext
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        if (this.configurations.containsKey(name)) {
            //如果有配置文件則注冊到當(dāng)前ApplicationContext應(yīng)用上下文中
            //如果@FeignClients配置了configuration則將被注冊到當(dāng)前ApplicationContext應(yīng)用上下文中
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        //@EnableFeignClients的defaultConfiguration配置將會(huì)被注冊到ApplicationContext中
        for (Map.Entry<String, C> 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.<String, Object>singletonMap(this.propertyName, name)));
        //設(shè)置父類ApplicationContext,這樣可以訪問父類Bean
        if (this.parent != null) {
            context.setParent(this.parent);
            context.setClassLoader(this.parent.getClassLoader());
        }
        //設(shè)置DisplayName屬性剔宪,格式為:FeignContext-FeignClient客戶端name/value屬性
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }
    //獲取實(shí)例
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        try {
            return context.getBean(type);
        }
        catch (NoSuchBeanDefinitionException e) {
            // ignore
        }
        return null;
    }
    ......
}

通過NamedContextFactory中的代碼片段以及注釋信息拂铡,我們更佳直觀的看出,和命名空間很像葱绒,每一個(gè)@FeignClient對應(yīng)的FeignClientSpecification對象都會(huì)生成一個(gè)專屬于這個(gè)@FeignClient的一個(gè)應(yīng)用上下文ApplicationContext失球,起到了隔離作用黔牵,生成ApplicationContext應(yīng)用上下文的步驟如下:

  1. 將@FeignClient注解生成的FeignClientSpecification對象添加到configurations配置Map中
  2. 根據(jù)@FeignClient的name阶剑、value屬性生成一個(gè)ApplicationContext應(yīng)用上下文
  3. 將@FeignClients中的configuration配置注冊到當(dāng)前ApplicationContext上下文中
  4. 將@EnableFeignClients的defaultConfiguration配置注冊到當(dāng)前上下文中
  5. 注冊屬性配置類、FeignClientsConfiguration客戶端配置類
  6. 添加MapPropertySource屬性配置
  7. 設(shè)置父類ApplicationContext,這樣可以訪問父類Bean
  8. 將當(dāng)前創(chuàng)建的ApplicationContext添加到上下文contexts中,每個(gè)@FeignClient都會(huì)生成一個(gè)ApplicationContext上下文,起隔離的作用
  9. 對外提供各種getInstance獲取實(shí)例方法

上面我們分析了FeignContext這個(gè)上下文,為每個(gè)@FeignClient客戶端創(chuàng)建一個(gè)ApplicationContext,外部通過這個(gè)工廠類獲取所需要的實(shí)例Bean,那下面我們繼續(xù)聊聊一個(gè)非常重要的目標(biāo)執(zhí)行類,HystrixTargeter具備了熔斷能力的執(zhí)行類铭若!

HystrixTargeter

class HystrixTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget<T> target) {
        //是否為Feign.Builder 類型镜雨,若不是則直接創(chuàng)建代理對象并執(zhí)行
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        //轉(zhuǎn)換為HystrixFeign.Builder類型
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        //獲取上下文id颓影,其實(shí)就是獲取的@FeignClient注解的name、value屬性值
        String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
                : factory.getContextId();
        //獲取SetterFactory,主要是HystrixCommand的groupKey、commandKey參數(shù),默認(rèn)setterFactory為空
        SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
        //setterFactory不為空就設(shè)置
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        //獲取降級方法,默認(rèn)為void初始狀態(tài)
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            //如果有設(shè)置了fallback偷遗,則使用
            return targetWithFallback(name, context, target, builder, fallback);
        }
        //獲取降級工廠類FallbackFactory,默認(rèn)為void初始狀態(tài)
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(name, context, target, builder,
                    fallbackFactory);
        }
        //調(diào)用HystrixFeign#build()
        return feign.target(target);
    }
    //具有FallbackFactory的目標(biāo)執(zhí)行類
    private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
            Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
            Class<?> fallbackFactoryClass) {
        //獲取降級工廠實(shí)例
        FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
                "fallbackFactory", feignClientName, context, fallbackFactoryClass,
                FallbackFactory.class);
        //返回具有FallbackFactory的代理實(shí)例
        return builder.target(target, fallbackFactory);
    }
    //具有Fallback的目標(biāo)執(zhí)行類
    private <T> T targetWithFallback(String feignClientName, FeignContext context,
            Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
            Class<?> fallback) {
        //獲取降級實(shí)例
        T fallbackInstance = getFromContext("fallback", feignClientName, context,
                fallback, target.type());
        //返回具有fallback的代理實(shí)例
        return builder.target(target, fallbackInstance);
    }
    //返回指定類型的實(shí)例
    private <T> T getFromContext(String fallbackMechanism, String feignClientName,
            FeignContext context, Class<?> beanType, Class<T> 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;
    }
    //根據(jù)@FeignClient注解的name、value屬性值獲取對應(yīng)beanType實(shí)例
    private <T> T getOptional(String feignClientName, FeignContext context,
            Class<T> beanType) {
        return context.getInstance(feignClientName, beanType);
    }
}

通過對HystrixTargeter的講解骏令,我們看到邏輯逐漸復(fù)雜起來摘昌,比較直觀的能看出這個(gè)類具備了Hystrix熔斷功能,再簡單點(diǎn),其實(shí)我們能這樣理解:HystrixTargeter會(huì)返回一個(gè)具備Hystrix熔斷功能的代理對象生真,內(nèi)部執(zhí)行順序就是請求先經(jīng)由Hystrix,然后再由LoadBalancerFeignClient進(jìn)行負(fù)載均衡請求最終的結(jié)果斤斧!


這里我們只是簡單的總結(jié)了HystrixTargeter類的作用,feign.target(target)這一句之后的邏輯相對更佳復(fù)雜看彼,及生成代理這個(gè)過程和代理內(nèi)部又做了哪些事情,將會(huì)放在后續(xù)的文章中進(jìn)行總結(jié)和講解逊脯!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淑际,一起剝皮案震驚了整個(gè)濱河市女阀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陈肛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橙数,死亡現(xiàn)場離奇詭異,居然都是意外死亡播演,警方通過查閱死者的電腦和手機(jī)选浑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叁温,“玉大人咖刃,你說我怎么就攤上這事箩帚《焯拢” “怎么了馁筐?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轮锥。 經(jīng)常有香客問我舍杜,道長,這世上最難降的妖魔是什么笆怠? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任冷守,我火速辦了婚禮蜡娶,結(jié)果婚禮上混卵,老公的妹妹穿的比我還像新娘。我一直安慰自己窖张,他們只是感情好幕随,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宿接,像睡著了一般赘淮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睦霎,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天梢卸,我揣著相機(jī)與錄音,去河邊找鬼副女。 笑死蛤高,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肮塞。 我是一名探鬼主播襟齿,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枕赵!你這毒婦竟也來了猜欺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拷窜,失蹤者是張志新(化名)和其女友劉穎开皿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篮昧,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赋荆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懊昨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窄潭。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖酵颁,靈堂內(nèi)的尸體忽然破棺而出嫉你,到底是詐尸還是另有隱情月帝,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布幽污,位于F島的核電站嚷辅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏距误。R本人自食惡果不足惜簸搞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望准潭。 院中可真熱鬧趁俊,春花似錦、人聲如沸惋鹅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闰集。三九已至沽讹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間武鲁,已是汗流浹背爽雄。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沐鼠,地道東北人挚瘟。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像饲梭,于是被迫代替她去往敵國和親乘盖。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容