Feign集成Hystrix源碼分析(掃描註解)
緣起:我司項目中集成了Feign與Hystrix全局降級處理,於是從源碼層面開始分析,自定義處理是怎麼運行的
@EnableFeignClients與@FeignClient
@EnableFeignClients -> FeignClientsRegistrar 掃描 @Feign註解的類
@FeignClient客戶端
參數配置
@FeignClient(contextId = "remoteBizMaintainConfig", value = ServiceNameConstants.MAINTENANCE_SERVICE)
public interface RemoteBizMaintainConfig {
/**
* 新增保養配置
*
* @param bizMaintainConfig
* @return
*/
@PostMapping("/bizmaintainconfig/save")
R save(@Valid @RequestBody BizMaintainConfig bizMaintainConfig);
}
@EnableFeignClients開啓Feign
在啓動類上加上@EnableFeignClients
,點進去,可以看到通過@Import
注入了FeignClientsRegistrar
@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 {};
}
FeignClientsRegistrar註冊客戶端
可以看到FeignClientsRegistrar
實現了ImportBeanDefinitionRegistrar
,我在之前的spring源碼章節有詳細介紹spring是怎麼處理@import
的spring加載流程之ConfigurationClassPostProcessor
該類主要實現了registerBeanDefinitions
方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 註冊@EnableFeignClients中的defaultConfiguration,默認爲空
registerDefaultConfiguration(metadata, registry);
// 註冊帶有@FeignClient的類
registerFeignClients(metadata, registry);
}
主要看registerFeignClients(metadata, registry)
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 定義scanner用於掃描
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 獲取@EnableFeignClients的參數
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 設置掃描過濾器,包含@FeignClient
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 添加掃描過濾器
scanner.addIncludeFilter(annotationTypeFilter);
// 獲取掃描的包名
basePackages = getBasePackages(metadata);
}
else {
// 一般不通過參數clients來配置掃描範圍,需要的可具體再看
.
.
.
}
// 循環需要掃描的包名
for (String basePackage : basePackages) {
// 掃描該包下帶有@FeignClient的所有類
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
// 循環帶有@FeignClient的類
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 獲取@FeignClient參數
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 獲取BeanName
String name = getClientName(attributes);
// 暫時不清楚註冊這幹嘛用的
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 註冊帶有@FeignClientBean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
上面一些方法,比如:
- basePackages = getBasePackages(metadata)
- name = getClientName(attributes);
通過源碼可以加深瞭解怎麼配置參數
例如:優先通過contextId
獲取BeanName
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());
}
FeignClientFactoryBean註冊的bean類型
最後執行registerFeignClient
時,通過FeignClientFactoryBean
來定義Bean
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 先獲取Bean定義,在配置Bean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
.
.
.
// 註冊Bean
}
FeignClientFactoryBean
實現了FactoryBean
,所有實現了該接口的Bean都是工廠Bean,通過重寫getObject()
方法返回真正的Bean。這一點跟mybatis掃描註冊mapper接口一樣,都是實現了FactoryBean
mybatis與spring的整合之MapperFactoryBean。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 獲取容器中定義好的Feign.Builder,同時獲取feign的yml配置
Feign.Builder builder = feign(context);
// 一般不配置url,配置name或value(服務名稱),交由erreka調用
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
// 帶上http
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 獲取容器中定義好的Targeter
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
看到最後通過getObject()
返回的BeanDefinition其實是targeter.target(...)
,就是帶有@FeignClient類的正真Bean實例
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target);
}
它,是一個接口,那麼Feign內部肯定有默認的實現
FeignClientsConfiguration自動配置類
Feign.Builder
需要設置feign.hystrix.enabled
爲true
開啓Hystrix,否則用不到降級配置
這裏注入的Feign.Builder
在FeignClientFactoryBean
被用到了
@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
FeignAutoConfiguration自動配置類
裝配FeignContext
與Targeter
,在FeignClientFactoryBean
會被用到了
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
同時看到會去加載feign
配置
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
FeignClientProperties
讀取feign配置
# feign 配置
feign:
hystrix:
enabled: true
okhttp:
enabled: true
httpclient:
enabled: false
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
FeignHttpClientProperties
FeignHttpClientProperties
是加載feign.httpclient
配置的,因爲用的是okhttp
,所以禁用了httpclient
。
以後抽時間瞭解下okhttp
與httpclient
DefaultTargeter
沒有做任何處理,這是沒有配置Hystrix
時的默認實現類
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
下章節,Targeter
的其他實現以及自定義
實現類Feign集成Hystrix源碼分析(Targeter實現類)