这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战
前言
在自动装配中,我们是用@EnableFeignClient作为例子的。
在自动装配中,我们可以知道:
- 具体定义的feign,在FeignClientsRegistrar中被定义为factoryBean,并将beanDefinition放入context中。
这里我们来看看这些bean是如何装配的。
版本:openFeign 2.2.0
接口装配
入口在这里:
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
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 {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
//在这里,会从包根目录中读取符合我们定义的那些feign(用@FeignClient标注的)
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
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");
//在这里,会从注解上读取相关的配置信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//根据上面读取到的信息,构造feignClient的BD
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
而这里beanDefinition的装配,实际是将值都放入了BeanDefinition的propertyValues中。
- 这里的代理类,是FeignClientFactoryBean。
注意:此时的url,如果没有显式标注,是没有的,如果要正常使用我们得去看具体实例化的地方。
既然是factoryBean,那么必然有一个getobject的方法,并且这个bean就是注入到对应类上的实例(没有初始化等中间点来做代理等操作的话):
//FeignClientFactoryBean
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//这里的feignContext是在FeignAutoConfiguration中作为bean注入的
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//这里就是对应着我们的一般情况:用eureka或者nacos之类的注册中心,没有写url的情况
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
//请求的url就会变成:http://我们指定的名字
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
//【1】
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));
}
-
【1】我们知道openFeign和hytrix整合了,因此看这里的是loadBalance:
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 = 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?"); } //这里依然是从context中拿实例,ceontext在加载的时候已把对应的feign都预先准备好了 protected <T> T getOptional(FeignContext context, Class<T> type) { return context.getInstance(this.contextId, type); } //上面的get方法,同样地target是从context中取的 protected <T> T get(FeignContext context, Class<T> type) { T instance = context.getInstance(this.contextId, type); if (instance == null) { throw new IllegalStateException( "No bean found of type " + type + " for " + this.contextId); } return instance; } //target.target:(HystrixTargeter) //TODO public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId(); SetterFactory setterFactory = getOptional(name, context, SetterFactory.class); if (setterFactory != null) { builder.setterFactory(setterFactory); } Class<?> fallback = factory.getFallback(); if (fallback != void.class) { return targetWithFallback(name, context, target, builder, fallback); } Class<?> fallbackFactory = factory.getFallbackFactory(); if (fallbackFactory != void.class) { return targetWithFallbackFactory(name, context, target, builder, fallbackFactory); } return feign.target(target); }-
而这里的feignContext中的target,其实是在FeignAutoConfiguration中配置的:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "feign.hystrix.HystrixFeign") protected static class HystrixFeignTargeterConfiguration { @Bean @ConditionalOnMissingBean public Targeter feignTargeter() { return new HystrixTargeter(); } } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("feign.hystrix.HystrixFeign") protected static class DefaultFeignTargeterConfiguration { @Bean @ConditionalOnMissingBean public Targeter feignTargeter() { return new DefaultTargeter(); } }可以看到,这里提供了2个targeter,如果带了hytrix,那么这个feignTargeter就是带负载均衡的,否则就是默认的。
脑筋急转弯:这里的getInstance是根据@Bean的类型来取的bean。
这里的@Configuration,是在扫描的时候被自动注入器(autoImportSelector)带入到Spring容器中的。
如果要开启hystrix的使用,那么需要在启动类上标注:@EnableCircuitBreaker,否则用的就是普通的feign。
-