Feign踩坑源碼解讀之@FeignClient注入容器
一. @EnableFeignClients
1.1.類介紹

從上面注釋可以看出是掃描聲明了@FeignClient接口的類,還引入了 FeignClientsRegistrar類,從字面意思可以看出是進(jìn)行了 FeignClient 客戶端類的注冊。
1.2.FeignClientsRegistrar 詳解
最主要的一個(gè)方法:registerBeanDefinitions注冊bean定義信息,主要功能是實(shí)現(xiàn)向容器注冊Feign客戶端配置信息和所有的使用了@FeignClient的類;

1.2.1.registerDefaultConfiguration()
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 獲取@EnableFeignClients注解的屬性和值
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 獲取屬性中的默認(rèn)配置 defaultConfiguration,name是main主程序
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//默認(rèn)配置信息進(jìn)行容器注冊
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
1.2.1.1.registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 創(chuàng)建一個(gè) FeignClientSpecification.class 構(gòu)造器
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 向容器中注冊默認(rèn)配置
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}1.2.1.2.registerBeanDefinition
核心代碼在:org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
............
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
//放到beanDefinitionMap,到AbstractApplicationContext#finishBeanFactoryInitialization(beanFactory)中取出進(jìn)行bean實(shí)例化
//此時(shí)是處于invokeBeanFactoryPostProcessors(beanFactory);階段,進(jìn)行BeanDefinitionRegistryPostProcessor的處理
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
..........
}1.2.2.registerFeignClients()

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//獲取一個(gè)掃描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
//設(shè)置資源路徑
scanner.setResourceLoader(this.resourceLoader);
//包路徑
Set<String> basePackages;
//獲取EnableFeignClients注解的參數(shù)和值,有5個(gè)值,其中clients對應(yīng)的class[0]是沒有值的
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//定義一個(gè) FeignClient 注解類型過濾器
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//clients為class[0],長度為0
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//FeignClient 注解過濾器添加到掃描器中
scanner.addIncludeFilter(annotationTypeFilter);
//獲取Application對應(yīng)的根路徑包
basePackages = getBasePackages(metadata);
}
//clients不為空:FeignClient 注解過濾器添加到掃描器中、獲取包路徑
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)));
}
//遍歷包路徑,獲取標(biāo)記@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");
//獲取@FeignClient的參數(shù)和值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//再次更新配置
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注冊
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
1.2.2.2.registerFeignClient
//定義一個(gè) FeignClientFactoryBean bean定義構(gòu)造器(重點(diǎn))
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
..........
//前面根據(jù)attributes進(jìn)行屬性賦值后注入到Spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);二.@FeignClient
從上面可以看到在注冊客戶端時(shí)注冊了一個(gè)FeignClientFactoryBean,所以FeignClient的獲取是從getObject()獲取的:

2.1.getTarget()
<T> T getTarget() {
//獲取創(chuàng)建feign實(shí)例的工廠
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//獲取feign構(gòu)造器
Feign.Builder builder = feign(context);
//url拼接
if (!StringUtils.hasText(this.url)) {
//拼接name
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
//拼接path
this.url += cleanPath();
//創(chuàng)建實(shí)例
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));
}2.1.1. feign()
//添加日志對象、編碼器、解碼器、解析規(guī)則器
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
//SpringMvcContract:對RequestMapping、RequestParam、RequestHeader等注解進(jìn)行解析
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}2.1.2. loadBalance()
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//根據(jù)context上下文獲取客戶端實(shí)例,client:TraceLoadBalancerFeignClient,負(fù)載均衡
Client client = getOptional(context, Client.class);
if (client != null) {
//客戶端創(chuàng)建
builder.client(client);
//獲取HystrixTargerer
Targeter targeter = get(context, Targeter.class);
//調(diào)用HystrixTargeter#target進(jìn)行實(shí)例創(chuàng)建
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?");
}


2.1.3. HystrixTargeter#target
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
// 不是同一種類型,進(jìn)入feign#target方法
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
......
}
2.1.4. Feign.Builder#target
public <T> T target(Target<T> target) {
return build().newInstance(target);
}2.1.4.1. Feign.Builder#target
public Feign build() {
//同步方法處理工廠,構(gòu)建了日志級(jí)別信息
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
//解析信息:編碼、解密、同步方法處理工廠
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//new對象
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}2.1.4.2.ReflectiveFeign#newInstance
@Override
public <T> T newInstance(Target<T> target) {
//得到feign類的定義方法
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//定義方法存放集合
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//默認(rèn)方法存放集合
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//對feign類方法進(jìn)行遍歷
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
//默認(rèn)方法
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//jdk動(dòng)態(tài)代理創(chuàng)建對象
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

總結(jié)
主要應(yīng)用了FactoryBean的特性getObject()進(jìn)行jdk動(dòng)態(tài)創(chuàng)建一個(gè)ReflectiveFeign的代理對象。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用Nacos動(dòng)態(tài)配置數(shù)據(jù)源的方法
這篇文章主要介紹了SpringBoot使用Nacos動(dòng)態(tài)配置數(shù)據(jù)源的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
使用java實(shí)現(xiàn)telnet-client工具分享
這篇文章主要介紹了使用java實(shí)現(xiàn)telnet-client工具,需要的朋友可以參考下2014-03-03
Java在Excel中添加水印的實(shí)現(xiàn)(單一水印、平鋪水印)
這篇文章主要介紹了Java在Excel中添加水印的實(shí)現(xiàn)(單一水印、平鋪水印),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
詳解Java?ReentrantReadWriteLock讀寫鎖的原理與實(shí)現(xiàn)
ReentrantReadWriteLock讀寫鎖是使用AQS的集大成者,用了獨(dú)占模式和共享模式。本文和大家一起理解下ReentrantReadWriteLock讀寫鎖的實(shí)現(xiàn)原理,需要的可以了解一下2022-10-10
java面試突擊之sleep和wait有什么區(qū)別詳析
按理來說sleep和wait本身就是八竿子打不著的兩個(gè)東西,但是在實(shí)際使用中大家都喜歡拿他們來做比較,或許是因?yàn)樗鼈兌伎梢宰尵€程處于阻塞狀態(tài),這篇文章主要給大家介紹了關(guān)于java面試突擊之sleep和wait有什么區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-02-02
Java子線程調(diào)用RequestContextHolder.getRequestAttributes()方法問題詳解
這篇文章主要介紹了Java子線程調(diào)用RequestContextHolder.getRequestAttributes()方法問題處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09
mybatis-plus分頁如何接收前端參數(shù)limit和page
這篇文章主要介紹了mybatis-plus分頁如何接收前端參數(shù)limit和page,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Spring之關(guān)于PropertyDescriptor的擴(kuò)展剖析
這篇文章主要介紹了Spring之關(guān)于PropertyDescriptor的擴(kuò)展剖析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07

