前面我們講解到@FeignClient在SpringBoot1.x與SpringBoot2.x版本之間不兼容抡蛙,無法復(fù)用的問題厢钧,并且使用了路徑覆蓋大法重寫@FeignClient這個注解類,使用這種方式基本零修改书释,本節(jié)我們則使用另外一種方式(繼承FeignClientsRegistrar)實現(xiàn)贝攒,這種方式可以了解@FeignClient注解注冊到Spring IOC的整個過程,更有助于我們后續(xù)分析Feign原理甩栈!
自定義注解實現(xiàn)版本兼容思路
- 自定義@EnableFeignClients注解(筆者嘗試過使用路徑覆蓋大法,發(fā)現(xiàn)無效)
- 繼承FeignClientsRegistrar糕再,掃描org.springframework.cloud.openfeign.FeignClient注解與org.springframework.cloud.netflix.feign.FeignClient注解
- 注冊@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)系如下:
- {@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}
我們按照映射優(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)容功戚!