springcloud openfeign原理-1-FeignClient初始化
必備知識:ImportBeanDefinitionRegistrar方式實現Bean的動態裝載
在ImportBeanDefinitionRegistrar方式實現Bean的動態裝載 一文中,@EnableFeignClients 這個註解的作用是開啟feignClient的掃描,註解定義如下:
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients
* @return list of default configurations
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
其中**@Import(FeignClientsRegistrar.class)** 匯入了一個配置類FeignClientsRegistrar,這個類實現了ImportBeanDefinitionRegistrar介面,那麼必然要重寫介面中的registerBeanDefinitions 方法:
//AnnotationMetadata metadata:註解中的後設資料
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry); // 註冊預設的配置資訊,
registerFeignClients(metadata, registry); // 註冊不同包下所有的FeignClient
}
這個方法裡面又做了兩件事:
- 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)));
}
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"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
上面程式碼中的for迴圈裡遍歷了掃描到的每個包,找到加了@FeignClient註解的介面,然後迪用下面的方法去註冊FeignClient
registerFeignClient(registry, annotationMetadata, attributes):
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class) 這行程式碼,使用到了FeignClientFactoryBean, 這是一個工廠Bean (class FeignClientFactoryBean
implements FactoryBean, InitializingBean, ApplicationContextAware )
FactoryBean有個特點,就是如果需要獲得這個bean的真正的例項,只有一個方法,就是通過 getObject方法來實現
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
因此,FeignClientFactoryBean中的getObject()方法一定會被呼叫到,它的實現如下,最終建立了一個複雜的bean進行返回:
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context information
*/
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("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();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url, but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) 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));
}
本文示例程式碼:https://download.csdn.net/download/weixin_41300437/13068346
相關文章
- SpringCloud-OpenFeignSpringGCCloud
- OpenFeign 原理
- SpringCloud(3)-OpenFeign相關配置SpringGCCloud
- SpringCloud OpenFeign Post請求的坑SpringGCCloud
- SpringCloud服務消費者-openFeign元件SpringGCCloud元件
- SpringCloud 9.OpenFeign服務介面呼叫SpringGCCloud
- Spring Cloud OpenFeign的原理(六)SpringCloud
- SpringCloud Alibaba實戰(8:使用OpenFeign服務呼叫)SpringGCCloud
- SpringCloud 服務負載均衡和呼叫 Ribbon、OpenFeignSpringGCCloud負載
- OpenFeign 服務註冊和呼叫原理
- SpringCloud實戰 | 第五篇:SpringCloud整合OpenFeign實現微服務之間的呼叫SpringGCCloud微服務
- 最適合新手入門的SpringCloud教程 7—OpenFeign「F版本」SpringGCCloud
- SpringCloud Alibaba(二) - Sentinel,整合OpenFeign,GateWay服務閘道器SpringGCCloudGateway
- springcloud,新建父工程(聚合工程)初始化專案SpringGCCloud
- SpringCloud——Feign例項及原理SpringGCCloud
- SpringCloud升級之路2020.0.x版-25.OpenFeign簡介與使用SpringGCCloud
- OpenFeign簡單使用
- OpenFeign 使用細節
- springcloud之hystrix原理和實踐總結SpringGCCloud
- SpringCloud升級之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)SpringGCCloud
- SpringCloud微服務實戰——搭建企業級開發框架(十一):整合OpenFeign用於微服務間呼叫SpringGCCloud微服務框架
- SpringCloud升級之路2020.0.x版-27.OpenFeign的生命週期-建立代理SpringGCCloud
- 【SpringCloud原理】Ribbon核心元件以及執行原理萬字原始碼剖析SpringGCCloud元件原始碼
- 06.OpenFeign介面呼叫
- SpringCloud微服務實戰——搭建企業級開發框架(十二):OpenFeign+Ribbon實現負載均衡SpringGCCloud微服務框架負載
- SpringCloud升級之路2020.0.x版-28.OpenFeign的生命週期-進行呼叫SpringGCCloud
- 終於搞懂了 Nacos、OpenFeign、Ribbon 等元件協調工作的原理,太強了!元件
- spring ioc原理-容器初始化的大致流程Spring
- 【c#版本Openfeign】Net8 自帶OpenFeign實現遠端介面呼叫C#
- Spring Cloud OpenFeign呼叫流程SpringCloud
- OpenFeign深入學習筆記筆記
- SpringCloud | 通過電商業務場景讓你徹底明白SpringCloud核心元件的底層原理SpringGCCloud元件
- Spring Cloud OpenFeign整合Protocol BufferSpringCloudProtocol
- OpenFeign 服務呼叫與負載負載
- Shiro原理解析(一)--過濾器的初始化過濾器
- 當面試官問我Mybatis初始化原理時,我笑了面試MyBatis
- Debian Linux的init程式初始化工作原理(轉)Linux
- Springcloud原始碼學習筆記1—— Zuul閘道器原理SpringGCCloud原始碼筆記Zuul