使用過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)單的解釋下具體做了哪些事情
- 掃描標(biāo)注了org.springframework.cloud.openfeign.FeignClient注解類
- 通過basePackages路徑添加添加滿足條件的BeanDefinition
- 通過BeanDefinition集合獲取org.springframework.cloud.openfeign.FeignClient注解對(duì)應(yīng)的屬性
- 注冊(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)注吧!