Spring Cloud Feign 分析(二)之FeignClient注解實(shí)現(xiàn)版本兼容

使用過Spring Cloud Netflix組件的同學(xué)都知道,Netflix組件的版本兼容性幾乎等于零脆烟,特別是大版本變化簡(jiǎn)直就是噩夢(mèng)山林,所以本節(jié)主要講解如何實(shí)現(xiàn)Feign的版本兼容,如何兼容SpringBoot1.x浩淘、SpringBoot2.x版本中的Feign使用捌朴!這樣我們?cè)赟pringBoot1.x版本使用@FeignClient在后續(xù)升級(jí)到SpringBoot2.x之后也不需要我們進(jìn)行單獨(dú)修改吴攒,畢竟現(xiàn)在微服務(wù)眾多,全部重新使用SpringBoot2.x版本的FeignClient也是一件不小的事情砂蔽,畢竟你改了代碼那就可能出現(xiàn)問題洼怔。所以這一節(jié)我們主要提供注解版本的兼容方式(基本零修改),順帶分析下FeignClientsRegistrar部分原理!


@EnableFeignClients注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] defaultConfiguration() default {};
    Class<?>[] clients() default {};
}

這里我們先看看FeignClient默認(rèn)實(shí)現(xiàn)左驾,通過在啟動(dòng)類上面注解這個(gè)類即可開啟FeignClient客戶端镣隶,那么這里我們看看原始FeignClientsRegistrar做了什么事情,為什么就不能兼容以前的版本呢诡右?


FeignClientsRegistrar#registerDefaultConfiguration

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    ......
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
    //根據(jù)@EnableFeignClients中參數(shù)defaultConfiguration注冊(cè)FeignClient的默認(rèn)配置(FeignClientsConfiguration)
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //注意這里的獲取的EnableFeignClients.class.getName()這個(gè)屬性
        //我們后面要做自定義注解的映射
        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();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }
    ......
}

這里我們簡(jiǎn)單的講解下安岂,從@EnableFeignClients注解中獲取defaultConfiguration參數(shù)并生產(chǎn)默認(rèn)配置,其中這個(gè)地方關(guān)注點(diǎn)metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true),后面我們的自定義注解會(huì)和這個(gè)形成映射關(guān)系


FeignClientsRegistrar#registerFeignClients

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    ......
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
    //注冊(cè)@FeignClient類到IOC容器中
    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {

        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            //獲取掃描器帆吻,
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            //掃描org.springframework.cloud.openfeign.FeignClient注解類
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                //添加滿足條件的BeanDefinition
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        else {
            for (Class<?> clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@FeignClient can only be specified on an interface");
                //獲取org.springframework.cloud.openfeign.FeignClient對(duì)應(yīng)的屬性
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));
                //注冊(cè)到IOC容器
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

這個(gè)地方我們也簡(jiǎn)單的解釋下具體做了哪些事情

  1. 掃描標(biāo)注了org.springframework.cloud.openfeign.FeignClient注解類
  2. 通過basePackages路徑添加添加滿足條件的BeanDefinition
  3. 通過BeanDefinition集合獲取org.springframework.cloud.openfeign.FeignClient注解對(duì)應(yīng)的屬性
  4. 注冊(cè)Bean到IOC容器中

FeignClient注解實(shí)現(xiàn)版本兼容

package org.springframework.cloud.netflix.feign;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@org.springframework.cloud.openfeign.FeignClient
public @interface FeignClient {

    /**
     * The name of the service with optional protocol prefix. Synonym for {@link #name()
     * name}. A name must be specified for all clients, whether or not a url is provided.
     * Can be specified as property key, eg: ${propertyKey}.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "name")
    String value() default "";

    /**
     * The service id with optional protocol prefix. Synonym for {@link #value() value}.
     *
     * @deprecated use {@link #name() name} instead
     */
    @Deprecated
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "serviceId")
    String serviceId() default "";

    /**
     * The service id with optional protocol prefix. Synonym for {@link #value() value}.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "value")
    String name() default "";

    /**
     * Sets the <code>@Qualifier</code> value for the feign client.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "qualifier")
    String qualifier() default "";

    /**
     * An absolute URL or resolvable hostname (the protocol is optional).
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "url")
    String url() default "";

    /**
     * Whether 404s should be decoded instead of throwing FeignExceptions
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "decode404")
    boolean decode404() default false;

    /**
     * A custom <code>@Configuration</code> for the feign client. Can contain override
     * <code>@Bean</code> definition for the pieces that make up the client, for instance
     * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
     *
     * @see FeignClientsConfiguration for the defaults
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "configuration")
    Class<?>[] configuration() default {};

    /**
     * Fallback class for the specified Feign client interface. The fallback class must
     * implement the interface annotated by this annotation and be a valid spring bean.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "fallback")
    Class<?> fallback() default void.class;

    /**
     * Define a fallback factory for the specified Feign client interface. The fallback
     * factory must produce instances of fallback classes that implement the interface
     * annotated by {@link FeignClient}. The fallback factory must be a valid spring
     * bean.
     *
     * @see feign.hystrix.FallbackFactory for details.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "fallbackFactory")
    Class<?> fallbackFactory() default void.class;

    /**
     * Path prefix to be used by all method-level mappings. Can be used with or without
     * <code>@RibbonClient</code>.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "path")
    String path() default "";

    /**
     * Whether to mark the feign proxy as a primary bean. Defaults to false.
     */
    @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "primary")
    boolean primary() default true;
}

這里我們采用路徑覆蓋大法域那,我們重新定義一個(gè)org.springframework.cloud.netflix.feign這個(gè)包名,然后定義一個(gè)FeignClient注解類猜煮,然后我們?cè)谶@個(gè)類上在引入一個(gè)注解@org.springframework.cloud.openfeign.FeignClient次员,把SpringBoot2.x版本的FeignClient引入進(jìn)來,這樣我們就實(shí)現(xiàn)了版本兼容王带,我們以前的SpringBoot1.x版本的可以不用修改就可以實(shí)現(xiàn)版本兼容淑蔚。然后在啟動(dòng)類上面使用標(biāo)準(zhǔn)的@EnableFeignClients注解

注意事項(xiàng)

spring:
  main:
    allow-bean-definition-overriding: true

在SpringBoot2.1之前,這個(gè)開關(guān)默認(rèn)是打開的愕撰,及可以重復(fù)定義Bean刹衫,但是在SpringBoot2.1之后這個(gè)配置默認(rèn)是false,所以如果我們的SpringBoot版本為2.1之后的搞挣,那么這個(gè)參數(shù)需要設(shè)置為true带迟,及允許后面的Bean可以覆蓋之前相同名稱的Bean,因?yàn)檫@個(gè)地方registerClientConfiguration會(huì)重復(fù)定義Bean柿究,建議根據(jù)情況配置邮旷,筆者這里的業(yè)務(wù)默認(rèn)都打開了的,畢竟我們的FeignClient配置是一樣的蝇摸,所以允許重復(fù)定義。


已經(jīng)講解到這里了办陷,這一節(jié)我們通過路徑覆蓋大法貌夕,重寫老版本的netflix.FeignClient注解在其之上加上新版本的openfeign.FeignClient注解來實(shí)現(xiàn)兼容,下一節(jié)我們將通過繼承FeignClientsRegistrar來實(shí)現(xiàn)的方式民镜,這種方式的實(shí)現(xiàn)能讓我們更加清楚的了解到@FeignClient的注冊(cè)方式啡专,如果覺得總結(jié)不錯(cuò)就點(diǎn)贊關(guān)注吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末制圈,一起剝皮案震驚了整個(gè)濱河市们童,隨后出現(xiàn)的幾起案子畔况,更是在濱河造成了極大的恐慌,老刑警劉巖慧库,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跷跪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡齐板,警方通過查閱死者的電腦和手機(jī)吵瞻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甘磨,“玉大人橡羞,你說我怎么就攤上這事〖糜撸” “怎么了卿泽?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滋觉。 經(jīng)常有香客問我又厉,道長,這世上最難降的妖魔是什么椎瘟? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任覆致,我火速辦了婚禮,結(jié)果婚禮上肺蔚,老公的妹妹穿的比我還像新娘煌妈。我一直安慰自己,他們只是感情好宣羊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布璧诵。 她就那樣靜靜地躺著,像睡著了一般仇冯。 火紅的嫁衣襯著肌膚如雪之宿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天苛坚,我揣著相機(jī)與錄音比被,去河邊找鬼。 笑死泼舱,一個(gè)胖子當(dāng)著我的面吹牛等缀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娇昙,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尺迂,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起噪裕,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蹲盘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后膳音,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體召衔,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年严蓖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薄嫡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颗胡,死狀恐怖毫深,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毒姨,我是刑警寧澤哑蔫,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站弧呐,受9級(jí)特大地震影響闸迷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俘枫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一腥沽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸠蚪,春花似錦今阳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蘸鲸,卻和暖如春妖谴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酌摇。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國打工膝舅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人妙痹。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓铸史,卻偏偏與公主長得像,于是被迫代替她去往敵國和親怯伊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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