Feign源碼解析——執(zhí)行過程
在上一篇Feign源碼解析——初始化流程中我們從注解為起點介紹了Feign是如何進行初始化參數(shù)、將Bean注入到Spring容器中的栖雾。接下來我們就介紹Feign是如何開始執(zhí)行的楞抡。
回顧
在上一篇中我們了解到了Feign將自身的參數(shù)注入到Spring容器中是分兩種類型進行注入的。
-
FeignClientSpecification
:主要為名字和Configuration的對應(yīng)析藕,包括@FeignClient
中的Configuration召廷,名字為value值,還包括@EnableFeignClients
注解中的name和DefaultConfiguration對應(yīng) -
FeignClientFactoryBean
:它其中包含了所有@FeignClient
注解上的參數(shù)账胧,他實現(xiàn)了Spring中的FactoryBean
接口竞慢。他是一個工廠類,用于創(chuàng)建實例治泥。
不了解FactoryBean
接口的筹煮,可以看如何使用Spring的FactoryBean接口
FeignClientFactoryBean
介紹
作為一個實現(xiàn)了FactoryBean
的工廠類,那么每次在Spring Context 創(chuàng)建實體類的時候會調(diào)用它的getObject()
方法居夹。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
private Class<?> type;
private String name;
private String url;
private String path;
private boolean decode404;
private ApplicationContext applicationContext;
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
-------其余代碼省略
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, 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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
-------其余代碼省略
}
這里的getObject()
其實就是將@FeinClient
中設(shè)置value值進行組裝起來败潦,此時或許會有疑問,因為在配置FeignClientFactoryBean
類時特意說過并沒有將Configuration傳過來吮播,那么Configuration中的屬性是如何配置的呢变屁?看其第一句是
FeignContext context = applicationContext.getBean(FeignContext.class);
意思就是從Spring容器中獲取FeignContext.class
的類,我們可以看下這個類是從哪加載的意狠。我們可以看FeignAutoConfiguration
此類粟关,源碼如下,我們可以看到在此類中自動配置了FeignContext
類,并且將FeignClientSpecification
類型的類全部加入此類的屬性中。
@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();
}
}
-------省略
}
至此我們已經(jīng)完成了配置屬性的裝配工作闷板,那么是如何執(zhí)行的呢澎灸?我們可以看getObject()
最后一句可以看到返回了Targeter.target
的方法。
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
那么這個Targeter
是哪來的遮晚?我們還是看上面的FeignAutoConfiguration
類性昭,可以看到其中有兩個Targeter
類,一個是DefaultTargeter
县遣,一個是HystrixTargeter
糜颠。當(dāng)配置了feign.hystrix.enabled= true
的時候,Spring容器中就會配置HystrixTargeter
此類萧求,如果為false那么Spring容器中配置的就是DefaultTargeter
我們以DefaultTargeter
為例介紹一下接下啦是如何通過創(chuàng)建代理對象的
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
public static class Builder {
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}
查看ReflectiveFeign
類中newInstance
方法是返回一個代理對象
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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)));
}
}
//設(shè)置攔截器
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;
}
最終都是執(zhí)行了SynchronousMethodHandler
攔截器中的invoke
方法
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
invoke
方法方法主要是應(yīng)用 encoder其兴,decoder 以及 retry 等配置, 并且自身對于調(diào)用結(jié)果有一定的處理邏輯夸政。我們最關(guān)心的請求實現(xiàn)元旬,實際上是在組裝 SynchronousMethodHandler 的 client 參數(shù)上,即前面提到的守问,如果當(dāng)前路徑里面有 Ribbon匀归,就是 LoadBalancerFeignClient,如果沒有耗帕,根據(jù)配置生成 ApacheHttpClient 或者 OKHttpClient穆端。在 Ribbon 里面,實現(xiàn)了 Eureka 服務(wù)發(fā)現(xiàn)以及進行請求等動作兴垦。當(dāng)然 Ribbon 里面還帶了負(fù)載均衡邏輯徙赢。
SynchronousMethodHandler
其實是不關(guān)心調(diào)用過程的,他只是處理調(diào)用的結(jié)果探越。調(diào)用過程是實現(xiàn)了Client
的實現(xiàn)類來做的狡赐。例如Ribbon
的LoadBalancerFeignClient
。
總結(jié)
到此為止钦幔,F(xiàn)eign的配置和執(zhí)行流程已經(jīng)簡單的說完了王暗。
調(diào)用接口為什么會直接發(fā)送請求婴程?
原因就是Spring掃描了@FeignClient
注解巷疼,并且根據(jù)配置的信息生成代理類汽绢,調(diào)用的接口實際上調(diào)用的是生成的代理類。
Feign的整體工作流程
- 掃描
@EnableFeignClients
注解中配置包路徑卷玉。 - 掃描
@FeignClient
注解哨颂,并將注解配置的信息注入到Spring容器中,類型為FeignClientFactoryBean
相种。 - 根據(jù)
FeignClientFactoryBean
的getObject()
方法得到不同動態(tài)代理的類威恼。 - 根據(jù)不同的代理執(zhí)行不同的
invoke()
方法。