Spring Cloud Feign 分析(三)之自定義注解實現(xiàn)版本兼容

前面我們講解到@FeignClient在SpringBoot1.x與SpringBoot2.x版本之間不兼容抡蛙,無法復(fù)用的問題厢钧,并且使用了路徑覆蓋大法重寫@FeignClient這個注解類,使用這種方式基本零修改书释,本節(jié)我們則使用另外一種方式(繼承FeignClientsRegistrar)實現(xiàn)贝攒,這種方式可以了解@FeignClient注解注冊到Spring IOC的整個過程,更有助于我們后續(xù)分析Feign原理甩栈!


自定義注解實現(xiàn)版本兼容思路

  1. 自定義@EnableFeignClients注解(筆者嘗試過使用路徑覆蓋大法,發(fā)現(xiàn)無效)
  2. 繼承FeignClientsRegistrar糕再,掃描org.springframework.cloud.openfeign.FeignClient注解與org.springframework.cloud.netflix.feign.FeignClient注解
  3. 注冊@FeignClient到Spring IOC容器中

package org.springframework.cloud.openfeign;

/**
 * 兼容SpringBoot1.x量没、SpringBoot2.x版本的<code>@FeignClient</code>注解
 * {@link org.springframework.cloud.netflix.feign.FeignClient}
 * {@link org.springframework.cloud.openfeign.FeignClient}
 * e.g:<code>@EnableCompositeFeignClients</code>替換<code>@EnableFeignClients</code>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CompositeFeignClientsRegistrar.class)
public @interface EnableCompositeFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] defaultConfiguration() default {};
    Class<?>[] clients() default {};
}

上面這個地方就是自定義一個注解,然后將啟動類上的@EnableFeignClients替換成我們的@EnableCompositeFeignClients注解突想,然后里面的方法定義直接拷貝
@EnableFeignClients注解類中的允蜈,然后@Import(CompositeFeignClientsRegistrar.class)加上這個自定義的注冊類


CompositeFeignClientsRegistrar

package org.springframework.cloud.openfeign;

/**
 * CompositeFeignClientsRegistrar
 * 混合FeignClient注冊實現(xiàn)
 * {@link org.springframework.cloud.netflix.feign.FeignClient}
 * {@link org.springframework.cloud.openfeign.FeignClient}
 */
public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        super.registerBeanDefinitions(new AnnotationMetadataWrapper(metadata), registry);
    }

    /**
     * 掃描注解,兼容以前的FeignClient
     * {@link org.springframework.cloud.netflix.feign.FeignClient}
     * {@link org.springframework.cloud.openfeign.FeignClient}
     *
     * @return ClassPathScanningCandidateComponentProvider
     */
    @Override
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        ClassPathScanningCandidateComponentProvider scanner = super.getScanner();
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(org.springframework.cloud.netflix.feign.FeignClient.class));
        return new ClassPathScanningCandidateComponentProviderWrapper(scanner);
    }
}

經(jīng)過分析FeignClientsRegistrar父類中的FeignClientsRegistrar#registerDefaultConfiguration()方法和FeignClientsRegistrar#registerFeignClients()方法,我們發(fā)現(xiàn)在getScanner()會掃描定義了@FeignClient注解的類蒿柳,所以這個地方我們對這個方法進(jìn)行重寫,然后把我們openfeign.FeignClient與netflix.feign.FeignClient加入到掃描路徑中漩蟆,然后返回一個ClassPathScanningCandidateComponentProviderWrapper包裝類


ClassPathScanningCandidateComponentProviderWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * ClassPathScanningCandidateComponentProviderWrapper
     * {@link FeignClientsRegistrar#registerFeignClients}
     * 重寫{@link ClassPathScanningCandidateComponentProvider#findCandidateComponents}
     * <p>
     * {@link org.springframework.cloud.openfeign.FeignClient}
     * {@link org.springframework.cloud.netflix.feign.FeignClient}
     * 實現(xiàn)老版本的<code>@FeignClient</code>對象生成
     */
    public static class ClassPathScanningCandidateComponentProviderWrapper
            extends ClassPathScanningCandidateComponentProvider {
        private ClassPathScanningCandidateComponentProvider scanner;

        public ClassPathScanningCandidateComponentProviderWrapper(
                ClassPathScanningCandidateComponentProvider scanner) {
            this.scanner = scanner;
        }

        /**
         * findCandidateComponents
         * 掃描{@link org.springframework.cloud.openfeign.FeignClient}
         * {@link org.springframework.cloud.netflix.feign.FeignClient}并生成BeanDefinition
         *
         * @param basePackage basePackage
         * @return Set<BeanDefinition>
         */
        @Override
        public Set<BeanDefinition> findCandidateComponents(String basePackage) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            return candidateComponents.stream().map(beanDefinition ->
                    new AnnotatedBeanDefinitionWrapper((AnnotatedBeanDefinition) beanDefinition)
            ).collect(Collectors.toSet());
        }

        /**
         * addIncludeFilter
         *
         * @param includeFilter includeFilter
         */
        @Override
        public void addIncludeFilter(TypeFilter includeFilter) {
            scanner.addIncludeFilter(includeFilter);
        }

        /**
         * setResourceLoader
         *
         * @param resourceLoader resourceLoader
         */
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            scanner.setResourceLoader(resourceLoader);
        }
    }
}

為什么需要包裝ClassPathScanningCandidateComponentProvider這個對象呢垒探?因為在FeignClientsRegistrar#registerFeignClients()方法中會掃描指定注解(@FeignClient)的路徑,然后調(diào)用findCandidateComponents()這個方法來生成滿足條件的BeanDefinition用于后面注冊到Spring IOC容器中,所以我們重寫getScanner()方法怠李,然后返回了一個ClassPathScanningCandidateComponentProviderWrapper包裝對象圾叼。


AnnotatedBeanDefinitionWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * AnnotatedBeanDefinitionWrapper
     * 主要用于生成{@link org.springframework.beans.factory.config.BeanDefinition}
     * <p>
     * {@link org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition}
     */
    @SuppressFBWarnings("SE_NO_SERIALVERSIONID")
    public static class AnnotatedBeanDefinitionWrapper extends GenericBeanDefinition
            implements AnnotatedBeanDefinition {

        private AnnotatedBeanDefinition beanDefinition;

        public AnnotatedBeanDefinitionWrapper(AnnotatedBeanDefinition beanDefinition) {
            this.beanDefinition = beanDefinition;
        }

        @Override
        public AnnotationMetadata getMetadata() {
            return new AnnotationMetadataWrapper(beanDefinition.getMetadata());
        }

        @Override
        public MethodMetadata getFactoryMethodMetadata() {
            return beanDefinition.getFactoryMethodMetadata();
        }

        @Override
        public boolean equals(Object other) {
            return beanDefinition.equals(other);
        }

        @Override
        public int hashCode() {
            return beanDefinition.hashCode();
        }
    }
}

在findCandidateComponents()這個方法中我們也返回一個AnnotatedBeanDefinitionWrapper包裝類,這個也是相同的疑問捺癞,為什么需要這個包裝類夷蚊?因為getMetadata()這個方法,我們需要返回一個注解元數(shù)據(jù)髓介,這個也簡單解釋下惕鼓,因為我需要實現(xiàn)注解映射,就是我需要將@EnableFeignClients注解映射成@EnableCompositeFeignClients這個注解的數(shù)據(jù)唐础,如果還是不好理解箱歧,那可以仔細(xì)研究下這一節(jié)的代碼片段矾飞!


AnnotationMetadataWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * 注解元數(shù)據(jù)包裝類
     * {@link FeignClientsRegistrar#registerFeignClients}{@link FeignClientsRegistrar#registerDefaultConfiguration}
     * <p>
     * 在{@link FeignClientsRegistrar}中指定了獲取{@link EnableFeignClients}{@link FeignClient}注解的參數(shù)配置
     * 為了兼容我們{@link org.springframework.cloud.netflix.feign.FeignClient}
     * {@link org.springframework.cloud.openfeign.FeignClient}注解,我們需要進(jìn)行注解映射
     * 包裝{@link org.springframework.core.type.StandardAnnotationMetadata}實現(xiàn)我們注解的映射匹配關(guān)系
     * <p>
     * {@link EnableFeignClients} => {@link org.springframework.cloud.openfeign.EnableCompositeFeignClients}
     * {@link FeignClient} => {@link org.springframework.cloud.openfeign.FeignClient}
     * {@link FeignClient} => {@link org.springframework.cloud.netflix.feign.FeignClient}
     * 映射關(guān)系按照優(yōu)先級執(zhí)行
     */
    public static class AnnotationMetadataWrapper implements AnnotationMetadata {
        /**
         * 用于兼容老版本的@FeignClient {@link org.springframework.cloud.netflix.feign.FeignClient}
         * 重新定義annotationTypes以下Key用于兼容
         * {@link FeignClientsRegistrar#registerFeignClients}{@link FeignClientsRegistrar#registerDefaultConfiguration}
         * <p>
         * 默認(rèn)規(guī)則優(yōu)先查找并使用 {@link org.springframework.cloud.openfeign.FeignClient}
         * 其次使用{@link org.springframework.cloud.netflix.feign.FeignClient}
         */
        private Map<String, List<String>> annotationTypes = MapBuilder.createWith(EnableFeignClients.class.getName(),
                Collections.singletonList(EnableCompositeFeignClients.class.getName()))
                .with(FeignClient.class.getName(), Arrays.asList(FeignClient.class.getName(),
                        org.springframework.cloud.netflix.feign.FeignClient.class.getName()))
                .with(FeignClient.class.getCanonicalName(), Arrays.asList(FeignClient.class.getCanonicalName(),
                        org.springframework.cloud.netflix.feign.FeignClient.class.getCanonicalName()))
                .build();
        private AnnotationMetadata metadata;

        public AnnotationMetadataWrapper(AnnotationMetadata metadata) {
            this.metadata = metadata;
        }

        protected List<String> wrapAnnotationName(String annotationName) {
            return annotationTypes.getOrDefault(annotationName, Collections.emptyList());
        }

        @Override
        public Set<String> getAnnotationTypes() {
            return metadata.getAnnotationTypes();
        }

        @Override
        public Set<String> getMetaAnnotationTypes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Set<String> metaAnnotationTypes = metadata.getMetaAnnotationTypes(name);
                if (CollectionUtils.isNotEmpty(metaAnnotationTypes)) {
                    return metaAnnotationTypes;
                }
            }
            return metadata.getMetaAnnotationTypes(annotationName);
        }

        @Override
        public boolean hasAnnotation(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasAnnotation = metadata.hasAnnotation(name);
                if (hasAnnotation) {
                    return true;
                }
            }
            return metadata.hasAnnotation(annotationName);
        }

        @Override
        public boolean hasMetaAnnotation(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasMetaAnnotation = metadata.hasMetaAnnotation(name);
                if (hasMetaAnnotation) {
                    return true;
                }
            }
            return metadata.hasMetaAnnotation(annotationName);
        }

        @Override
        public boolean hasAnnotatedMethods(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasAnnotatedMethods = metadata.hasAnnotatedMethods(name);
                if (hasAnnotatedMethods) {
                    return true;
                }
            }
            return metadata.hasAnnotatedMethods(annotationName);
        }

        @Override
        public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Set<MethodMetadata> annotatedMethods = metadata.getAnnotatedMethods(name);
                if (CollectionUtils.isNotEmpty(annotatedMethods)) {
                    return annotatedMethods;
                }
            }
            return metadata.getAnnotatedMethods(annotationName);
        }

        @Override
        public MergedAnnotations getAnnotations() {
            return metadata.getAnnotations();
        }

        @Override
        public boolean isAnnotated(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean isAnnotated = metadata.isAnnotated(name);
                if (isAnnotated) {
                    return true;
                }
            }
            return metadata.isAnnotated(annotationName);
        }

        @Override
        public Map<String, Object> getAnnotationAttributes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(name);
                if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) {
                    return annotationAttributes;
                }
            }
            return metadata.getAnnotationAttributes(annotationName);
        }

        @Override
        public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(name, classValuesAsString);
                if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) {
                    return annotationAttributes;
                }
            }
            return metadata.getAnnotationAttributes(annotationName, classValuesAsString);
        }

        @Override
        public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                MultiValueMap<String, Object> allAnnotationAttributes = metadata.getAllAnnotationAttributes(name);
                if (Objects.nonNull(allAnnotationAttributes) && !allAnnotationAttributes.isEmpty()) {
                    return allAnnotationAttributes;
                }
            }
            return metadata.getAllAnnotationAttributes(annotationName);
        }

        @Override
        public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName,
                                                                        boolean classValuesAsString) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                MultiValueMap<String, Object> allAnnotationAttributes =
                        metadata.getAllAnnotationAttributes(name, classValuesAsString);
                if (Objects.nonNull(allAnnotationAttributes) && !allAnnotationAttributes.isEmpty()) {
                    return allAnnotationAttributes;
                }
            }
            return metadata.getAllAnnotationAttributes(annotationName, classValuesAsString);
        }

        @Override
        public String getClassName() {
            return metadata.getClassName();
        }

        @Override
        public boolean isInterface() {
            return metadata.isInterface();
        }

        @Override
        public boolean isAnnotation() {
            return metadata.isAnnotation();
        }

        @Override
        public boolean isAbstract() {
            return metadata.isAbstract();
        }

        @Override
        public boolean isConcrete() {
            return metadata.isConcrete();
        }

        @Override
        public boolean isFinal() {
            return metadata.isFinal();
        }

        @Override
        public boolean isIndependent() {
            return metadata.isIndependent();
        }

        @Override
        public boolean hasEnclosingClass() {
            return metadata.hasEnclosingClass();
        }

        @Override
        public String getEnclosingClassName() {
            return metadata.getEnclosingClassName();
        }

        @Override
        public boolean hasSuperClass() {
            return metadata.hasSuperClass();
        }

        @Override
        public String getSuperClassName() {
            return metadata.getSuperClassName();
        }

        @Override
        public String[] getInterfaceNames() {
            return metadata.getInterfaceNames();
        }

        @Override
        public String[] getMemberClassNames() {
            return metadata.getMemberClassNames();
        }
    }
}

AnnotationMetadataWrapper這個注解元數(shù)據(jù)包裝類其實就是一個注解映射作用呀邢,因為在FeignClientsRegistrar父類中會獲取@EnableFeignClients洒沦、@FeignClient注解對應(yīng)的屬性,所以這里我們就做一個映射關(guān)系价淌,映射關(guān)系如下:

  1. {@link EnableFeignClients} => {@link org.springframework.cloud.openfeign.EnableCompositeFeignClients}
  2. {@link FeignClient} => {@link org.springframework.cloud.openfeign.FeignClient}
  3. {@link FeignClient} => {@link org.springframework.cloud.netflix.feign.FeignClient}

我們按照映射優(yōu)先級獲取映射之后注解的屬性返回申眼,默認(rèn)優(yōu)先查找openfeign.FeignClient的屬性,當(dāng)查詢不到再查詢netflix.feign.FeignClient注解的屬性蝉衣。


至此括尸,到了文章末尾,也大概總結(jié)下吧买乃,我們使用自定義注解這種方式呢姻氨,我們可以更加靈活的管理和實現(xiàn),也有助于我們閱讀后續(xù)的Feign章節(jié)剪验。當(dāng)然這樣的方式可能對不熟悉源碼的同學(xué)不太友好肴焊,如果對FeignClientsRegistrar相關(guān)邏輯不清楚的可以參閱Spring Cloud Feign 分析(一)之FeignClient注冊過程,能加快我們理解本節(jié)內(nèi)容功戚!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娶眷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啸臀,更是在濱河造成了極大的恐慌届宠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘粒,死亡現(xiàn)場離奇詭異豌注,居然都是意外死亡,警方通過查閱死者的電腦和手機灯萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門轧铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旦棉,你說我怎么就攤上這事齿风。” “怎么了绑洛?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵救斑,是天一觀的道長。 經(jīng)常有香客問我真屯,道長脸候,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮纪他,結(jié)果婚禮上鄙煤,老公的妹妹穿的比我還像新娘。我一直安慰自己茶袒,他們只是感情好梯刚,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著薪寓,像睡著了一般亡资。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上向叉,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天锥腻,我揣著相機與錄音,去河邊找鬼母谎。 笑死瘦黑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奇唤。 我是一名探鬼主播幸斥,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咬扇!你這毒婦竟也來了甲葬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤懈贺,失蹤者是張志新(化名)和其女友劉穎经窖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梭灿,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡画侣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了堡妒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棉钧。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涕蚤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情的诵,我是刑警寧澤万栅,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站西疤,受9級特大地震影響烦粒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一扰她、第九天 我趴在偏房一處隱蔽的房頂上張望兽掰。 院中可真熱鬧,春花似錦徒役、人聲如沸孽尽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杉女。三九已至,卻和暖如春鸳吸,著一層夾襖步出監(jiān)牢的瞬間熏挎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工晌砾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羽历,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓驾孔,卻偏偏與公主長得像瓶盛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乖寒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354