日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

SpringCloud之Feign源码分析

發(fā)布時(shí)間:2025/3/21 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringCloud之Feign源码分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

啟動(dòng)時(shí)Feign的處理
啟動(dòng)類(lèi)上使用了@EnableFeignClients注解,我們來(lái)看下這個(gè)注解在哪里使用了,使用idea只要在EnableFeignClients類(lèi)上按住command同時(shí)點(diǎn)擊類(lèi)名就可以查看到這個(gè)類(lèi)在哪里使用了,發(fā)現(xiàn)除了啟動(dòng)類(lèi),只在FeignClientsRegistrar類(lèi)中引用了EnableFeignClients。

debug可以發(fā)現(xiàn),當(dāng)應(yīng)用啟動(dòng)時(shí)會(huì)首先調(diào)用FeignClientsRegistrar的registerBeanDefinitions()方法。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注冊(cè)默認(rèn)配置信息
registerDefaultConfiguration(metadata, registry);
//注冊(cè)每個(gè)聲明為Feign Client的類(lèi)
registerFeignClients(metadata, registry);
}

主要看下registerFeignClients()方法。

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//獲取掃描classpath下component組件的掃描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

Set basePackages;
//獲取啟動(dòng)類(lèi)上配置的@EnableFeignClients注解的屬性
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//從剛才獲取的@EnableFeignClients注解的屬性中獲取clients屬性配置的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get(“clients”);
if (clients == null || clients.length == 0) {
//如果clients沒(méi)配置
//掃描器增加要掃描的過(guò)濾器(掃描被@FeignClient注解修飾的類(lèi))
scanner.addIncludeFilter(annotationTypeFilter);
//獲取配置的掃描包的路徑,如果沒(méi)配置,默認(rèn)為啟動(dòng)類(lèi)的包路徑
basePackages = getBasePackages(metadata);
}
else {
final Set clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
//如果啟動(dòng)類(lèi)配置了clients屬性的值,將配置的client所在的包名加到掃描器掃描的包中
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)));
}

//遍歷包名,掃描@FeignClient注解修飾的類(lèi)(怎么掃描到?前面加了掃描@FeignClient注解的IncludeFilter)

for (String basePackage : basePackages) {
Set candidateComponents = scanner
.findCandidateComponents(basePackage);
//遍歷掃描出來(lái)的@FeignClient注解修飾的類(lèi)
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
//校驗(yàn)@FeignClient注解修飾的類(lèi)是否是interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//斷言,@FeignClient注解修飾的類(lèi)必須是interface
Assert.isTrue(annotationMetadata.isInterface(),
“@FeignClient can only be specified on an interface”);

//先獲取@FeignClient注解的屬性值Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());//獲得@FeignClient配置的client 的名稱(chēng)(name或value或serviceId)String name = getClientName(attributes);//注冊(cè)feign client的配置信息registerClientConfiguration(registry, name,attributes.get("configuration"));//注冊(cè)feign clientregisterFeignClient(registry, annotationMetadata, attributes);}}

}
}

//將feign client交由spring管理,聲明為spring的bean
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//創(chuàng)建FeignClientFactoryBean,包含將feign client的注解屬性信息存入FeignClientFactoryBean中
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
//校驗(yàn)feign client的配置,配置的fallback及fallbackFatory必須是實(shí)現(xiàn)類(lèi)
validate(attributes);
//將@FeignClient注解配置的屬性放入FeignClientFactoryBean的BeanDefinitionBuilder中
definition.addPropertyValue(“url”, getUrl(attributes));
definition.addPropertyValue(“path”, getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue(“name”, name);
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 = name + “FeignClient”;
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

boolean primary = (Boolean)attributes.get(“primary”); // has a default, won’t be null

beanDefinition.setPrimary(primary);

String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}

BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注冊(cè)bean到spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

在spring容器啟動(dòng)時(shí)會(huì)調(diào)用FeignClientFactoryBean的getObject()方法(只有在其他bean注入feign client時(shí)才會(huì)調(diào)用),看下FeignClientFactoryBean的getObject()方法做了哪些處理。

public Object getObject() throws Exception {
//直接調(diào)用了getTarget()方法
return getTarget();
}

/**

  • @param the target type of the Feign client

  • @return a {@link Feign} client created with the specified data and the context information
    */
    T getTarget() {
    //這個(gè)FeignContext在FeignAutoConfiguration配置中已經(jīng)聲明了,所以可以直接用applicationContext獲取bean
    FeignContext context = applicationContext.getBean(FeignContext.class);
    //配置feign 的decoder、encoder、retryer、contract、RequestInterceptor等
    //這些有默認(rèn)配置,在FeignAutoConfiguration及FeignClientsConfiguration中有默認(rèn)配置
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
    //如果@FeignClient注解上指定了url,其實(shí)除非本地調(diào)試,一般不建議指定URL
    String url;
    if (!this.name.startsWith(“http”)) {
    url = “http://” + this.name;
    }
    else {
    url = this.name;
    }
    //處理URL,沒(méi)配置URL時(shí),這里的URL形式為http://name+/path
    url += cleanPath();
    //使用負(fù)載均衡處理feign 請(qǐng)求
    return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
    this.name, url));
    }
    //配置了FeignClient的具體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));
    }

decoder:將http請(qǐng)求的response轉(zhuǎn)換成對(duì)象
encoder:將http請(qǐng)求的對(duì)象轉(zhuǎn)換成http request body
contract:校驗(yàn)Feign Client上的注解及value值是否合法
retryer:定義http請(qǐng)求如果失敗了是否應(yīng)該重試以及重試間隔、方式等等
RequestInterceptor:feign發(fā)起請(qǐng)求前的攔截器,可以全局定義basic auth、發(fā)起請(qǐng)求前自動(dòng)添加header等等
從@FeignClient注解上是否指定URL,feign的處理分成了兩部分,如果未指定URL,則使用負(fù)載均衡去發(fā)送請(qǐng)求,指定URL,只會(huì)向指定的URL發(fā)送請(qǐng)求。

一般是不指定URL的,接下來(lái)先看下,不指定具體URL時(shí),feign的處理。

protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget target) {
//默認(rèn)client為L(zhǎng)oadBalancerFeignClient,為啥?參見(jiàn)DefaultFeignLoadBalancedConfiguration
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//這個(gè)Targeter默認(rèn)為DefaultTargeter,參見(jiàn)FeignAutoConfiguration
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?”);
}

Targeter默認(rèn)為DefaultTargeter,client為L(zhǎng)oadBalancerFeignClient。再看下DefaultTargeter.target()方法

public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target) {
return feign.target(target);
}

Feign.target()方法。

public T target(Target target) {
return build().newInstance(target);
}

ReflectiveFeign.newInstance()方法。這里為什么是ReflectiveFeign?參考Feign.build()方法

public T newInstance(Target target) {
//這個(gè)apply方法就是ReflectiveFeign中的apply方法,返回了每個(gè)方法的調(diào)用包裝類(lèi)SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List defaultMethodHandlers = new LinkedList();
//這個(gè)target.type()返回的就是聲明@FeignClient注解所在的class
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} 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)));
}
}
//返回了ReflectiveFeign.FeignInvocationHandler對(duì)象,這個(gè)對(duì)象的invoke方法其實(shí)就是調(diào)用了SynchronousMethodHandler.invoke方法
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;
}

public Map<String, MethodHandler> apply(Target key) {
//獲取類(lèi)上的方法的元數(shù)據(jù),如返回值類(lèi)型,參數(shù)類(lèi)型,注解數(shù)據(jù)等等
List metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
//這個(gè)factory是SynchronousMethodHandler.Factory,create方法返回了一個(gè)SynchronousMethodHandler對(duì)象
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}

簡(jiǎn)單總結(jié)下啟動(dòng)時(shí)Feign所做的處理:

獲取@EnableFeignClients注解配置的掃描包路徑,如果沒(méi)配置,默認(rèn)為啟動(dòng)類(lèi)的包路徑。
獲得掃描包路徑下@FeignClient修飾的類(lèi)
校驗(yàn)@FeignClient修飾的類(lèi),包括類(lèi)必須是interface,以及@FeignClient的fallback及fallbackFactory配置的必須是接口的實(shí)現(xiàn)類(lèi)等
將@FeignClient修飾的類(lèi)交由spring管理,聲明為bean,其他bean注入FeignClient時(shí)注入的其實(shí)是當(dāng)前FeignClient的代理類(lèi),這個(gè)代理類(lèi)包裝在Targeter內(nèi)部,Targeter被注入到引用的bean中。
這樣做的好處是:在程序中使用Feign Client時(shí)就可以像其他spring 管理的bean一樣直接注入即可。

例如:

@Autowired
private CartFeignClient cartFeignClient;

@PostMapping("/toCart/{productId}")
public ResponseEntity addCart(@PathVariable(“productId”) Long productId) throws InterruptedException {
cartFeignClient.addCart(productId);
return ResponseEntity.ok(productId);
}

調(diào)用Feign Client時(shí)的feign的處理
剛分析了應(yīng)用啟動(dòng)及bean注入FeignClient時(shí)feign的處理,知道注入的其實(shí)是Targeter類(lèi),Targetr類(lèi)包裝了FeignCLient的proxy,proxy內(nèi)部綁定了methodHandler為SynchronousMethodHandler。接下來(lái)仔細(xì)分析下整個(gè)實(shí)際調(diào)用過(guò)程的處理。

前面提到feign實(shí)際處理方法調(diào)用的methodHandler是SynchronousMethodHandler。

實(shí)際上,首先調(diào)用的是ReflectiveFeign的靜態(tài)內(nèi)部類(lèi)FeignInvocationHandler,這個(gè)類(lèi)實(shí)現(xiàn)了JDK的InvocationHandler接口,在調(diào)用代理類(lèi)的方法時(shí)會(huì)被調(diào)用FeignInvocationHandler的invoke方法。

FeignInvocationHandler的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (“equals”.equals(method.getName())) {
try {
Object
otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if (“hashCode”.equals(method.getName())) {
return hashCode();
} else if (“toString”.equals(method.getName())) {
return toString();
}
//除了equals、hashCode、toString方法外,其他方法都走dispatch.get(method).invoke(args)方法。
//點(diǎn)擊這個(gè)方法的實(shí)現(xiàn)類(lèi),就可以追到 SynchronousMethodHandler的invoke方法了。
return dispatch.get(method).invoke(args);
}

可以看到除了equals、hashCode、toString方法外,其他方法都走dispatch.get(method).invoke(args)方法。
點(diǎn)擊這個(gè)方法的實(shí)現(xiàn)類(lèi),就可以追到 SynchronousMethodHandler的invoke方法了。所以這里其實(shí)只是簡(jiǎn)單起到轉(zhuǎn)發(fā)的作用。

SynchronousMethodHandler的invoke方法。

public Object invoke(Object[] argv) throws Throwable {
//根據(jù)調(diào)用參數(shù)創(chuàng)建一個(gè)RequestTemplate,用來(lái)具體處理http調(diào)用請(qǐng)求
RequestTemplate template = buildTemplateFromArgs.create(argv);
//克隆出一個(gè)一模一樣的Retryer,用來(lái)處理調(diào)用失敗后的重試
Retryer retryer = this.retryer.clone();
while (true) {
try {
//發(fā)送http request以及處理response等
return executeAndDecode(template);
} catch (RetryableException e) {
//處理重試次數(shù)、重試間隔等等
retryer.continueOrPropagate(e);
continue;
}
}
}

先來(lái)看下如何創(chuàng)建的RequestTemplate。

ReflectiveFeign的內(nèi)部靜態(tài)類(lèi)BuildTemplateByResolvingArgs的create方法。

public RequestTemplate create(Object[] argv) {
//獲取methodMetada的template,這個(gè)RequestTemplate是可變的,跟隨每次調(diào)用參數(shù)而變。
RequestTemplate mutable = new RequestTemplate(metadata.template());
if (metadata.urlIndex() != null) {
//處理@PathVariable在URL上插入的參數(shù)
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, “URI parameter %s was null”, urlIndex);
mutable.insert(0, String.valueOf(argv[urlIndex]));
}
//處理調(diào)用方法的param參數(shù),追加到URL ?后面的參數(shù)
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
//處理query參數(shù)以及body內(nèi)容
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
//當(dāng) RequestTemplate處理完參數(shù)后,再處理@QueryMap注入的參數(shù),以便優(yōu)先于任意值。
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}

if (metadata.headerMapIndex() != null) {
//處理RequestTemplate的header內(nèi)容
template = addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}

return template;
}

可以看到,第一步是根據(jù)調(diào)用時(shí)的參數(shù)等構(gòu)造了RequestTemplate的param、body、header等內(nèi)容。

再看executeAndDecode方法。

SynchronousMethodHandler的executeAndDecode方法。

Object executeAndDecode(RequestTemplate template) throws Throwable {
//構(gòu)造Request,將RequestTemplate中的參數(shù)等放入Request中
Request request = targetRequest(template);
Response response;
try {
//這個(gè)client默認(rèn)實(shí)現(xiàn)是Client接口中的Defalut,實(shí)現(xiàn)是通過(guò)HttpURLConnection發(fā)送請(qǐng)求
//另一種是LoadBalancerFeignClient,默認(rèn)也是Client接口中的Defalut,可以通過(guò)配置指定為Apache的HTTPClient,也可以指定為OKhttp來(lái)發(fā)送請(qǐng)求,在每個(gè)具體實(shí)現(xiàn)中來(lái)通過(guò)ribbon實(shí)現(xiàn)負(fù)載均衡,負(fù)載到集群中不同的機(jī)器,這里不再發(fā)散
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
throw errorExecuting(request, e);
}
boolean shouldClose = true;
try {
//處理response的返回值
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
//根據(jù)狀態(tài)碼處理下response
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
}
}

總結(jié)一下:

代理類(lèi)先調(diào)用到FeignInvocationHandler的invoke方法,而這個(gè)invoke方法相當(dāng)于直接調(diào)用了SynchronousMethodHandler的invoke方法。
SynchronousMethodHandler的invoke方法主要是構(gòu)造了RequestTemplate以及出現(xiàn)異常重試的Retryer,最后根據(jù)構(gòu)造的RequestTemplate發(fā)起了http請(qǐng)求以及decode。
構(gòu)造RequestTemplate時(shí),根據(jù)傳入的參數(shù)動(dòng)態(tài)構(gòu)建URL中的參數(shù)(@PathVarible)以及URL ?追加的參數(shù),還有body等等,最后再處理@QueryMap注入的參數(shù),以保證優(yōu)先級(jí)最高。
發(fā)起http請(qǐng)求時(shí),沒(méi)有負(fù)載均衡時(shí),默認(rèn)是通過(guò)JDK的HttpURLConnection發(fā)送請(qǐng)求,另一種就是LoadBalancerFeignClient各種實(shí)現(xiàn)類(lèi),如Apache的HTTPClient,以及OKhttp等,這些實(shí)現(xiàn)也是通過(guò)ribbon動(dòng)態(tài)指定服務(wù)器IP地址,以達(dá)到負(fù)載均衡的作用。

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的SpringCloud之Feign源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。