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实现类)