FeignClient原始碼深度解析
微信公眾號:吉姆餐廳ak 學習更多原始碼知識,歡迎關注。 全文共16984字左右。
概述
springCloud feign主要對netflix feign進行了增強和包裝,本篇從原始碼角度帶你過一遍裝配流程,揭開feign底層的神祕面紗。 主要包括feign整合ribbon,hystrix,sleuth,以及生成的代理類最終注入到spring容器的過程。篇幅略長,耐心讀完,相信你會有所收穫。
Feign架構圖
一些核心類及大致流程:
大體步驟: 一、註冊FeignClient配置類和FeignClient BeanDefinition 二、例項化Feign上下文物件FeignContext 三、建立 Feign.builder 物件 四、生成負載均衡代理類 五、生成預設代理類 六、注入到spring容器
原始碼分析
主要圍繞上面6個步驟詳細分析。
一、註冊FeignClient配置類和FeignClient BeanDefinition
從啟動類註解開始,來看下 @EnableFeignClients
註解:
-
@EnableFeignClients
-
public class MyApplication {
-
}
這是在啟動類開啟feign裝配的註解,跟進該註解,看看做了什麼:
-
@Import(FeignClientsRegistrar.class)
-
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
-
ResourceLoaderAware, BeanClassLoaderAware {
-
-
// patterned after Spring Integration IntegrationComponentScanRegistrar
-
// and RibbonClientsConfigurationRegistgrar
-
private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class);
-
private ResourceLoader resourceLoader;
-
-
private ClassLoader classLoader;
-
-
public FeignClientsRegistrar() {
-
}
-
-
@Override
-
public void setResourceLoader(ResourceLoader resourceLoader) {
-
this.resourceLoader = resourceLoader;
-
}
-
-
@Override
-
public void setBeanClassLoader(ClassLoader classLoader) {
-
this.classLoader = classLoader;
-
}
-
-
@Override
-
public void registerBeanDefinitions(AnnotationMetadata metadata,
-
BeanDefinitionRegistry registry) {
-
//1、先註冊預設配置
-
registerDefaultConfiguration(metadata, registry);
-
//2、註冊所有的feignClient beanDefinition
-
registerFeignClients(metadata, registry);
-
}
-
//...
-
}
我們分別來看一下上面 registerBeanDefinitions
中的兩個方法: 1) 註冊預設配置方法: registerDefaultConfiguration
:
-
private void registerDefaultConfiguration(AnnotationMetadata metadata,
-
BeanDefinitionRegistry registry) {
-
Map<String, Object> defaultAttrs = metadata
-
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
-
-
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
-
String name;
-
if (metadata.hasEnclosingClass()) {
-
name = "default." + metadata.getEnclosingClassName();
-
}
-
else {
-
name = "default." + metadata.getClassName();
-
}
-
// name 預設以 default 開頭,後續會根據名稱選擇配置
-
registerClientConfiguration(registry, name,
-
defaultAttrs.get("defaultConfiguration"));
-
}
-
}
上述方法為讀取啟動類上面 @EnableFeignClients
註解中宣告feign相關配置類,預設name為default,一般情況下無需配置。用預設的 FeignAutoConfiguration
即可。 上面有個比較重要的方法:註冊配置 registerClientConfiguration
,啟動流程一共有兩處讀取feign的配置類,這是第一處。根據該方法看一下:
-
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
-
Object configuration) {
-
BeanDefinitionBuilder builder = BeanDefinitionBuilder
-
.genericBeanDefinition(FeignClientSpecification.class);
-
builder.addConstructorArgValue(name);
-
builder.addConstructorArgValue(configuration);
-
registry.registerBeanDefinition(
-
name + "." + FeignClientSpecification.class.getSimpleName(),
-
builder.getBeanDefinition());
-
}
上面將bean配置類包裝成 FeignClientSpecification
,注入到容器。該物件非常重要,包含FeignClient需要的重試策略,超時策略,日誌等配置,如果某個服務沒有設定,則讀取預設的配置。
2、掃描FeignClient
該方法主要是掃描類路徑,對所有的FeignClient生成對應的 BeanDefinition
:
-
public void registerFeignClients(AnnotationMetadata metadata,
-
BeanDefinitionRegistry registry) {
-
-
//...
-
//獲取掃描目錄下面所有的bean deanDefinition
-
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
-
registerFeignClient(registry, annotationMetadata, attributes);
-
}
-
}
-
}
-
}
可以看到上面又呼叫了 registerClientConfiguration
註冊配置的方法,這裡是第二處呼叫。這裡主要是將掃描的目錄下,每個專案的配置類載入的容器當中。 註冊到容器中,什麼時候會用到呢?具體又如何使用呢?彆著急,後面會有介紹。
我們先會回到繼續主流程,繼續看註冊feignClient的方法,跟進 registerFeignClient
:
-
private void registerFeignClient(BeanDefinitionRegistry registry,
-
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
-
String className = annotationMetadata.getClassName();
-
//宣告代理類名稱
-
BeanDefinitionBuilder definition = BeanDefinitionBuilder
-
.genericBeanDefinition(FeignClientFactoryBean.class);
-
//logger.info("TEX do some replacement");
-
//attributes.put("value", ((String)attributes.get("value")).replace('_','-'));
-
validate(attributes);
-
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.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
-
-
String alias = name + "FeignClient";
-
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
-
beanDefinition.setPrimary(true);
-
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
-
new String[] { alias });
-
//將bean definition加入到spring容器
-
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
-
}
劃重點,上面出現了一行相當關鍵程式碼:
-
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
springCloud FeignClient其實是利用了spring的代理工廠來生成代理類,所以這裡將所有的 feignClient
的描述資訊 BeanDefinition
設定為 FeignClientFactoryBean
型別,該類又繼承 FactoryBean
,很明顯,這是一個代理類。 在spring中, FactoryBean
是一個工廠bean,用作建立代理bean,所以得出結論,feign將所有的feignClient bean包裝成 FeignClientFactoryBean
。掃描方法到此結束。
代理類什麼時候會觸發生成呢? 在spring重新整理容器時,當例項化我們的業務service時,如果發現註冊了FeignClient,spring就會去例項化該FeignClient,同時會進行判斷是否是代理bean,如果為代理bean,則呼叫 FeignClientFactoryBean
的 T getObject() throws Exception;
方法生成代理bean。
先來隆重介紹一下 FeignClientFactoryBean
,後面四步都基於此類。
先看一下代理feignClient代理生成入口: getObject
方法:
-
@Override
-
public Object getObject() throws Exception {
-
// 二、例項化Feign上下文物件FeignContext
-
FeignContext context = applicationContext.getBean(FeignContext.class);
-
// 三、生成builder物件,用來生成feign
-
Feign.Builder builder = feign(context);
-
-
// 判斷生成的代理物件型別,如果url為空,則走負載均衡,生成有負載均衡功能的代理類
-
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));
-
}
-
//如果指定了url,則生成預設的代理類
-
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
-
this.url = "http://" + this.url;
-
}
-
String url = this.url + cleanPath();
-
// 五、生成預設代理類
-
return targeter.target(this, builder, context, new HardCodedTarget<>(
-
this.type, this.name, url));
-
}
getObject()
邏輯比較多,每一行都會做一些初始化配置,來逐步分析。
二、例項化Feign上下文物件FeignContext
上述方法中第一行便是例項化 FeignContext
:
-
FeignContext context = applicationContext.getBean(FeignContext.class);
獲取 FeignContext
物件,如果沒有例項化,則主動例項化,如下:
-
@Configuration
-
@ConditionalOnClass(Feign.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();
-
//將feign的配置類設定到feign的容器當中
-
context.setConfigurations(this.configurations);
-
return context;
-
}
-
}
可以看到feign的配置類設定到feign的容器當中,而集合中的元素 正是上面我們提到的兩處呼叫 registerClientConfiguration
方法新增進去的,前後呼應。
然而,當我們引入了 sleuth
之後,獲取的 feignContext
確是 TraceFeignClientAutoConfiguration
中配置的例項 sleuthFeignContext
:
可以看到上面建立了一個 TraceFeignContext
例項,因為該物件繼承 FeignContext
,同時又加了 @Primary
註解,所以在上面第2步中通過型別獲取: applicationContext.getBean(FeignContext.class);
,最終拿到的是 TraceFeignContext
。
三、構造 FeignBuilder
繼續跟進該方法:
Feign.Builder builder = feign(context);
-
protected Feign.Builder feign(FeignContext context) {
-
Logger logger = getOptional(context, Logger.class);
-
-
if (logger == null) {
-
logger = new Slf4jLogger(this.type);
-
}
-
-
// 1、構造 Feign.Builder
-
Feign.Builder builder = get(context, Feign.Builder.class)
-
// required values
-
.logger(logger)
-
.encoder(get(context, Encoder.class))
-
.decoder(get(context, Decoder.class))
-
.contract(get(context, Contract.class));
-
-
-
// 2、設定重試策略,log等元件
-
-
//設定log級別
-
Logger.Level level = getOptional(context, Logger.Level.class);
-
if (level != null) {
-
builder.logLevel(level);
-
}
-
//設定重試策略
-
Retryer retryer = getOptional(context, Retryer.class);
-
if (retryer != null) {
-
builder.retryer(retryer);
-
}
-
//feign的錯誤code解析介面
-
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
-
if (errorDecoder != null) {
-
builder.errorDecoder(errorDecoder);
-
}
-
//超時時間設定,連線超時時間:connectTimeout預設10s,請求請求超時時間:readTimeout預設60s
-
Request.Options options = getOptional(context, Request.Options.class);
-
if (options != null) {
-
builder.options(options);
-
}
-
//攔截器設定,可以看出攔截器也是可以針對單獨的feignClient設定
-
Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
-
this.name, RequestInterceptor.class);
-
if (requestInterceptors != null) {
-
builder.requestInterceptors(requestInterceptors.values());
-
}
-
-
if (decode404) {
-
builder.decode404();
-
}
-
-
return builder;
-
}
上述程式碼有兩處邏輯,分別來看:
1、 Feign.Builder builder = get(context, Feign.Builder.class)
,又會有以下三種情況:
1)單獨使用Feign,沒有引入 sleuth
、 hystrix
: 通過載入FeignClientsConfiguration的配置建立 Feign
的靜態內部類: Feign.Builder
-
@Bean
-
@Scope("prototype")
-
@ConditionalOnMissingBean
-
public Feign.Builder feignBuilder(Retryer retryer) {
-
return Feign.builder().retryer(retryer);
-
}
2)引入了 hystrix
,沒有引入 sleuth
: 通過載入 FeignClientsConfiguration
的配置建立 HystrixFeign
的靜態內部類: HystrixFeign.Builder
-
@Configuration
-
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
-
protected static class HystrixFeignConfiguration {
-
@Bean
-
@Scope("prototype")
-
@ConditionalOnMissingBean
-
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
-
public Feign.Builder feignHystrixBuilder() {
-
return HystrixFeign.builder();
-
}
-
}
3)同時引入 hystrix
和 sleuth
: 載入 TraceFeignClientAutoConfiguration
的配置建立: HystrixFeign.Builder
:注意:
-
TraceFeignClientAutoConfiguration
的配置類載入一定是在FeignClientsConfiguration
之前(先載入先生效),而FeignClientsConfiguration
載入是通過FeignAutoConfiguration
完成的,所以上圖中引入了條件註解:-
@AutoConfigureBefore({FeignAutoConfiguration.class})
-
-
建立建立的
builder
物件和第二種情況一下,只是做了一層包裝:
-
final class SleuthFeignBuilder {
-
-
private SleuthFeignBuilder() {}
-
-
static Feign.Builder builder(Tracer tracer, HttpTraceKeysInjector keysInjector) {
-
return HystrixFeign.builder()
-
//各元件`client,retryer,decoder`進行增強,裝飾器模式。
-
.client(new TraceFeignClient(tracer, keysInjector))
-
.retryer(new TraceFeignRetryer(tracer))
-
.decoder(new TraceFeignDecoder(tracer))
-
.errorDecoder(new TraceFeignErrorDecoder(tracer));
-
}
-
}
2、設定重試策略,log等元件 Feign.builder在獲取之後又分別指定了重試策略,日誌級別,錯誤程式碼code等,在上一步中呼叫 SleuthFeignBuilder.build()
時已經設定過預設值了,這裡為什麼要重複設定呢?
我們跟進去get()方法,一探究竟:
-
protected <T> T get(FeignContext context, Class<T> type) {
-
//根據name,也就是服務名稱來生成builder
-
T instance = context.getInstance(this.name, type);
-
if (instance == null) {
-
throw new IllegalStateException("No bean found of type " + type + " for "
-
+ this.name);
-
}
-
return instance;
-
}
-
public <T> T getInstance(String name, Class<T> type) {
-
//這裡獲取AnnotationConfigApplicationContext容器
-
AnnotationConfigApplicationContext context = getContext(name);
-
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
-
type).length > 0) {
-
return context.getBean(type);
-
}
-
return null;
-
}
-
-
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
-
-
protected AnnotationConfigApplicationContext getContext(String name) {
-
if (!this.contexts.containsKey(name)) {
-
synchronized (this.contexts) {
-
if (!this.contexts.containsKey(name)) {
-
//這裡建立容器createContext(name)
-
this.contexts.put(name, createContext(name));
-
}
-
}
-
}
-
return this.contexts.get(name);
-
}
重點來了,上述程式碼將FeignContext做了快取,每個服務對應一個FeignContext,服務名作為key。 繼續跟進 createContext(name)
方法:
-
protected AnnotationConfigApplicationContext createContext(String name) {
-
//注意:這裡的容器並不是spring的容器,而是每次都重新建立一個
-
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
-
//載入每個服務對應的配置類
-
if (this.configurations.containsKey(name)) {
-
for (Class<?> configuration : this.configurations.get(name)
-
.getConfiguration()) {
-
context.register(configuration);
-
}
-
}
-
//載入啟動類@EnableFeignClients註解指定的配置類
-
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
-
if (entry.getKey().startsWith("default.")) {
-
for (Class<?> configuration : entry.getValue().getConfiguration()) {
-
context.register(configuration);
-
}
-
}
-
}
-
//註冊預設的配置類:FeignClientsConfiguration
-
context.register(PropertyPlaceholderAutoConfiguration.class,
-
this.defaultConfigType);
-
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
-
this.propertySourceName,
-
Collections.<String, Object> singletonMap(this.propertyName, name)));
-
if (this.parent != null) {
-
// Uses Environment from parent as well as beans
-
context.setParent(this.parent);
-
}
-
//重新整理容器
-
context.refresh();
-
return context;
-
}
可以看到上述AnnotationConfigApplicationContext容器並非spring容器,只是利用了spring重新整理容器的方法來例項化配置類,以服務名作為key,配置隔離。
重點來了,上面載入配置的順序為:先載入每個服務的配置類,然後載入啟動類註解上的配置類,最後載入預設的配置類。這樣做有什麼好處? spring重新整理容器的方法也是對所有的bean進行了快取,如果已經建立,則不再例項化。所以優先選取每個FeignClient的配置類,最後預設的配置類兜底。
所以這也證明了 sleuth
的配置一定在 feign
的配置類之前載入。 至此, FeignBuilder
構造流程結束。
四、生成負載均衡代理類
再貼一下生成代理類的入口:
-
//判斷url是否為空
-
if (!StringUtils.hasText(this.url)) {
-
//......
-
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
-
this.name, url));
-
}
-
//......
-
return targeter.target(this, builder, context, new HardCodedTarget<>(
-
this.type, this.name, url));
這裡有個重要判斷:判斷FeignClient宣告的url是否為空,來判斷具體要生成的代理類。如下: 這麼做有什麼意義? 1)如果為空,則預設走Ribbon代理,也就是這個入口,會有載入ribbon的處理。 @FeignClient("MyFeignClient")
2)如果不為空,指定url,則走預設生成代理類的方式,也就是所謂的硬編碼。 @FeignClient(value = "MyFeignClient",url = "http://localhost:8081")
這樣處理方便開發人員進行測試,無需關注註冊中心,直接http呼叫,是個不錯的開發小技巧。
生產環境也可以用上述第二種方式,指定域名的方式。
我們跟進 loadBalance
方法:
-
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
-
HardCodedTarget<T> target) {
-
//獲得FeignClient
-
Client client = getOptional(context, Client.class);
-
if (client != null) {
-
builder.client(client);
-
return targeter.target(this, builder, context, target);
-
}
-
throw new IllegalStateException(
-
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
-
}
Client client = getOptional(context, Client.class);
這裡會從 FeignContext
上下文中獲取 Client
物件,該物件有三種例項,具體是哪個實現呢?
這裡又會有三種情況: 1)沒有整合 ribbon
、 sleuth
: 獲取預設的 Client
: Default
例項。
2)整合了 ribbon
,沒有整合 sleuth
: 獲取 LoadBalanceFeignClient
例項。
3)整合了 ribbon
和 sleuth
: 會獲取 TraceFeignClient
例項,該例項是對 LoadBalanceFeignClient
的一種包裝,實現方式通過 BeanPostProcessor
實現: FeignBeanPostProcessor
中定義了包裝邏輯:
-
@Override
-
public Object postProcessBeforeInitialization(Object bean, String beanName)
-
throws BeansException {
-
return this.traceFeignObjectWrapper.wrap(bean);
-
}
通過 wrap
方法最終返回 TraceFeignClient
例項。
繼續回到主流程,先來看下 Targeter
介面:
-
interface Targeter {
-
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
-
HardCodedTarget<T> target);
-
}
該物件定義在 FeignClientFactoryBean
靜靜態程式碼塊中:
-
private static final Targeter targeter;
-
-
static {
-
Targeter targeterToUse;
-
//判斷類路徑是否引入了hystrixFeign
-
if (ClassUtils.isPresent("feign.hystrix.HystrixFeign",
-
FeignClientFactoryBean.class.getClassLoader())) {
-
targeterToUse = new HystrixTargeter();
-
}
-
else {
-
targeterToUse = new DefaultTargeter();
-
}
-
targeter = targeterToUse;
-
}
這裡會初始化 Targeter
,該類是生成feign代理類的工具類,有兩種實現,正是上面的 HystrixTargeter
, DefaultTargeter
。 因為我們引入了 hystrix
,所以 Targeter
實現類為 HystrixTargeter
。我們繼續跟進 targeter.target
方法:
-
public <T> T target(Target<T> target) {
-
return build().newInstance(target);
-
}
上面通過 build()
方法獲取生成代理類的工具類 ReflectiveFeign
,再通過 newInstance
正式建立代理類。 繼續跟進:
-
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);
-
}
這裡會建立Feign的方法工廠 synchronousMethodHandlerFactory
, Feign
通過該工廠為每個方法建立一個 methodHandler
,每個 methodHandler
中包含Feign對應的配置: retryer
、 requestInterceptors
等。
繼續跟進 newInstance
方法:
-
public <T> T newInstance(Target<T> target) {
-
//建立所有的 MethodHandler
-
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;
-
//判斷是否啟用預設handler
-
} 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)));
-
}
-
}
-
//建立InvocationHandler,接收請求,轉發到methodHandler
-
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;
-
}
InvocationHandler
最終建立的例項為 HystrixInvocationHandler
,核心方法如下:
-
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setter) {
-
@Override
-
protected Object run() throws Exception {
-
try {
-
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
-
} catch (Exception e) {
-
throw e;
-
} catch (Throwable t) {
-
throw (Error) t;
-
}
-
}
-
-
@Override
-
protected Object getFallback() {
-
//......
-
}
-
};
整個流程:Feign呼叫方發起請求,傳送至hystrix的HystrixInvocationHandler,通過服務名稱,找到對應方法的methodHandler,methodHandler中封裝了loadBalanceClient、retryer、RequestInterceptor等元件,如果引入了sleuth,這幾個元件均是sleuth的包裝類。然後通過以上元件構造 http
請求完成整個過程。
五、生成預設代理類
理解了第四步的邏輯,生成預設代理類就很容易理解了,唯一不同點就是 client
的實現類為 loadBalanceClient
。
注意:不管是哪種代理類,最終發起請求還是由
Feign.Default
中的execute
方法完成,預設使用HttpUrlConnection
實現。
六、注入spring容器
總結:通過 spring refresh()
方法,觸發 FeignClientFactoryBean.getObject()
方法獲得了代理類,然後完成注入 spring
容器的過程。該實現方式同 Dubbo
的實現方式類似,有興趣的可以自行研究噢。
掃一掃,支援下作者吧
(轉載本站文章請註明作者和出處 方誌朋的部落格)