本文主要針對 spring-cloud-starter-openfeign 的 2.2.3.RELEASE 版本進行源碼的解析。
本文在原文的基礎上,增加了一些代碼圖示以及必要的結構整理。原文閱讀
OpenFeign是什么東東
作為Spring Cloud的子項目之一,Spring Cloud OpenFeign以將OpenFeign集成到Spring Boot應用中的方式,為微服務架構下服務之間的調用提供了解決方案。首先,利用了OpenFeign的聲明式方式定義Web服務客戶端;其次還更進一步,通過集成Ribbon或Eureka實現負載均衡的HTTP客戶端。
對於未接觸過 Feign的小伙伴可以參考簡介 進行一些基礎知識的了解。
實現原理
講清楚OpenFeign的實現原理,我們要從這兩個步驟講起:
- FeignClient的bean注冊過程,以及動態代理過程;
- FeignClient的調用過程。
1. FeignClient的bean注冊過程
@EnableFeignClients
想要集成 Feign 客戶端,需要我們通過注解 @EnableFeignClients 來開啟。這個注解開啟了FeignClient的解析過程。這個注解的聲明如下,它用到了一個@Import注解,我們知道Import是用來導入一個配置類的,接下來去看一下FeignClientsRegistrar的定義:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar,它是一個動態注入bean的接口,Spring Boot啟動的時候,會去調用這個類中的registerBeanDefinitions來實現動態Bean的裝載。它的作用類似於ImportSelector。
對於動態注入不清楚的小伙伴可以參考 : https://www.cnblogs.com/wuzhenzhao/p/9151673.html 。
然后就會進入 FeignClientsRegistrar# registerBeanDefinitions 。registerDefaultConfiguration 方法內部從 SpringBoot 啟動類上檢查是否有@EnableFeignClients
, 有該注解的話, 則完成 Feign 框架相關的一些配置內容注冊registerFeignClients 方法內部從 classpath 中, 掃描獲得 @FeignClient
修飾的類, 將類的內容解析為 BeanDefinition , 最終通過調用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 將解析處理過的 FeignClientBeanDeifinition 添加到 spring 容器中.
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注冊@EnableFeignClients中定義defaultConfiguration屬性下的類,包裝成FeignClientSpecification,注冊到Spring容器。
//在@FeignClient中有一個屬性:configuration,這個屬性是表示各個FeignClient自定義的配置類,后面也會通過調用registerClientConfiguration方法來注冊成FeignClientSpecification到容器。
//所以,這里可以完全理解在@EnableFeignClients中配置的是做為兜底的配置,在各個@FeignClient配置的就是自定義的情況。
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
這里面需要重點分析的就是 registerFeignClients 方法,這個方法主要是掃描類路徑下所有的@FeignClient注解,然后進行動態Bean的注入。它最終會調用 registerFeignClient 方法。
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"));
// 注冊Feign 客戶端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
registerFeignClient 在這個方法中,就是去組裝BeanDefinition,也就是Bean的定義,然后注冊到Spring IOC容器。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 省略代碼.....
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
我們關注一下,BeanDefinitionBuilder是用來構建一個BeanDefinition的,它是通過 genericBeanDefinition 來構建的,並且傳入了一個FeignClientFactoryBean的類,代碼如下。
/**
* Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}.
* @param beanClass the {@code Class} of the bean that the definition is being created for
*/
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
動態代理過程詳解
我們可以發現,FeignClient被動態注冊成了一個FactoryBean.
Spring Cloud FengnClient實際上是利用Spring的代理工廠來生成代理類,所以在這里地方才會把所有的FeignClient的BeanDefinition設置為FeignClientFactoryBean類型,而FeignClientFactoryBean繼承自FactoryBean,它是一個工廠Bean。在Spring中,FactoryBean是一個工廠Bean,用來創建代理Bean。工廠 Bean 是一種特殊的 Bean, 對於 Bean 的消費者來說, 他邏輯上是感知不到這個 Bean 是普通的 Bean 還是工廠 Bean, 只是按照正常的獲取 Bean 方式去調用, 但工廠bean 最后返回的實例不是工廠Bean 本身, 而是執行工廠 Bean 的 getObject 邏輯返回的示例。
簡單來說,FeignClient標注的這個接口,會通過FeignClientFactoryBean.getObject()這個方法獲得一個代理對象。
FeignClientFactoryBean.getObject:
getObject調用的是getTarget方法,它從applicationContext取出FeignContext,FeignContext繼承了NamedContextFactory,它是用來統一維護feign中各個feign客戶端相互隔離的上下文。
FeignContext注冊到容器是在FeignAutoConfiguration上完成的。
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
在初始化FeignContext時,會把configurations在容器中放入FeignContext中。configurations 的來源就是在前面registerFeignClients方法中將@FeignClient的配置 configuration。
接着,構建feign.builder,在構建時會向FeignContext獲取配置的Encoder,Decoder等各種信息。FeignContext在上文中已經提到會為每個Feign客戶端分配了一個容器,它們的父容器就是spring容器。
配置完Feign.Builder之后,再判斷是否需要LoadBalance,如果需要,則通過LoadBalance的方法來設置。實際上他們最終調用的是Target.target()方法。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//實例化Feign上下文對象FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);//構建Builder對象
//如果url為空,則走負載均衡,生成有負載均衡功能的代理類
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));
}
//如果指定了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));
}
loadBalance :生成具備負載均衡能力的feign客戶端,為feign客戶端構建起綁定負載均衡客戶端。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
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?");
}
Client client = (Client)this.getOptional(context, Client.class); 從上下文中獲取一個 Client,默認是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration這個自動裝配類中,通過Import實現的
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
.....
}
這里的通過 DefaultFeignLoadBalancedConfiguration 注入客戶端 Client 的實現
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
接下去進入 targeter.target(this, builder, context, target) ,攜帶着構建好的這些對象去創建代理實例 ,這里有兩個實現 HystrixTargeter 、DefaultTargeter 很顯然,我們沒有配置 Hystrix ,這里會走 DefaultTargeter
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
然后會來到 feign.Feign.Builder#target(feign.Target
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, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
最終會調用 ReflectiveFeign.newInstance
這個方法是用來創建一個動態代理的方法,在生成動態代理之前,會根據Contract協議(協議解析規則,解析接口類的注解信息,解析成內部的MethodHandler的處理方式。
從實現的代碼中可以看到熟悉的Proxy.newProxyInstance方法產生代理類。而這里需要對每個定義的接口方法進行特定的處理實現,所以這里會出現一個MethodHandler的概念,就是對應方法級別的InvocationHandler。
public <T> T newInstance(Target<T> target) {
// 解析接口注解信息
//根據接口類和Contract協議解析方式,解析接口類上的方法和注解,轉換成內部的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;
} 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 handler = factory.create(target, methodToHandler);
// 基於Proxy.newProxyInstance 為接口類創建動態實現,將所有的請求轉換給InvocationHandler 處理。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
targetToHandlersByName.apply(target) :根據Contract協議規則,解析接口類的注解信息,解析成內部表現:targetToHandlersByName.apply(target);會解析接口方法上的注解,從而解析出方法粒度的特定的配置信息,然后生產一個SynchronousMethodHandler 然后需要維護一個<method,MethodHandler>的map,放入InvocationHandler的實現FeignInvocationHandler中。
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.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, target);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
SpringMvcContract
當前Spring Cloud 微服務解決方案中,為了降低學習成本,采用了Spring MVC的部分注解來完成 請求協議解析,也就是說 ,寫客戶端請求接口和像寫服務端代碼一樣:客戶端和服務端可以通過SDK的方式進行約定,客戶端只需要引入服務端發布的SDK API,就可以使用面向接口的編碼方式對接服務。
2.OpenFeign調用過程
OpenFeign調用過程圖示:
在前面的分析中,我們知道OpenFeign最終返回的是一個 ReflectiveFeign.FeignInvocationHandler 的對象。那么當客戶端發起請求時,會進入到 FeignInvocationHandler.invoke 方法中,這個大家都知道,它是一個動態代理的實現。
@Override
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();
}
// 利用分發器篩選方法,找到對應的handler 進行處理
return dispatch.get(method).invoke(args);
}
而接着,在invoke方法中,會調用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 會返回一個SynchronousMethodHandler,進行攔截處理。這個方法會根據參數生成完成的RequestTemplate對象,這個對象是Http請求的模版,代碼如下。
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
經過上述的代碼,我們已經將restTemplate拼裝完成,上面的代碼中有一個 executeAndDecode() 方法,該方法通過RequestTemplate生成Request請求對象,然后利用Http Client獲取response,來獲取響應信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//轉化為Http請求報文
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//發起遠程通信
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
//獲取返回結果
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// .......
}
經過上面的分析,這里的 client.execute 的 client 的類型是LoadBalancerFeignClient,這里就很自然的進入 LoadBalancerFeignClient#execute。
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
其實這個execute里面得流程就是 Ribbon 的那一套。我們可以簡單的看一下。首先是構造URI,構造RibbonRequest,選擇 LoadBalance,發起調用。
來看一下lbClient 選擇負載均衡器的時候做了什么
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
可以得出的結論就是 this.factory.getLoadBalancer(clientName) 跟Ribbon 源碼里的獲取方式一樣,無疑這里獲取的就是默認的 ZoneAwareLoadBalancer。然后包裝成一個 FeignLoadBalancer 進行返回
既然負載均衡器選擇完了,那么一定還有個地方通過該負載去選擇一個服務,接着往下看:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
上面這段代碼就是通過獲取到的負載進行執行請求,但是這個時候 服務還沒有選擇,我們跟進去 submit 請求看一看究竟:
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// .........
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
//........
});
// .......
}
可以看到這里有個 selectServer的方法 ,跟進去:
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// Various Supported Cases
// The loadbalancer to use and the instances it has is based on how it was registered
// In each of these cases, the client might come in using Full Url or Partial URL
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// ............
} else {
// ...........if (shouldInterpretAsVip) {
Server svc = lb.chooseServer(loadBalancerKey);
if (svc != null){
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("using LB returned Server: {} for request: {}", svc, original);
return svc;
} else {
// just fall back as real DNS
logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
}
} else {
// consult LB to obtain vipAddress backed instance given full URL
//Full URL execute request - where url!=vipAddress
logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
}
}
// ..........
return new Server(host, port);
}
可以看到的是這里獲取到了之前構造好的 ZoneAwareLoadBalancer 然后調用 chooseServer 方法獲取server ,這個是跟Ribbon 中是一樣的流程,這里就不贅述了。
獲取到了server 后,會回調先前 executeWithLoadBalancer 方法里構造的 ServerOperation 的 call 方法:
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
然后會執行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 進行最后的調用,實際上這里走的是 FeignLoadBalancer#execute
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
而這里調用的 request.client().execute(request.toRequest(), options) 則是 DefaultFeignLoadBalancedConfiguration 注入的 LoadBalancerFeignClient ,在構造 LoadBalancerFeignClient 的時候 ,傳遞了個 feign.Client.Default ,然后利用 feign.Client.Default 構造了一個 RibbonRequest。
所以這里走 feign.Client.Default#execute :
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
利用 JDK 提供的 HttpURLConnection 發起遠程的 HTTP通訊。至此發起請求的流程就完成了。下面附上一張這個過程的流程圖,對於Ribbon的調用過程請參考 :Ribbon 源碼分析。
3. OpenFeign Configuration
針對 feign 的 Configuration ,官方給我們提供了很多的個性化配置,具體可以參考 org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration
public static class FeignClientConfiguration {
// 日志
private Logger.Level loggerLevel;
// 連接超時
private Integer connectTimeout;
private Integer readTimeout;
//重試
private Class<Retryer> retryer;
//解碼
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
// 編碼
private Boolean decode404;
private Class<Decoder> decoder;
private Class<Encoder> encoder;
// 解析
private Class<Contract> contract;
private ExceptionPropagationPolicy exceptionPropagationPolicy;
}
這里舉個簡單的例子,以Logger 為例。我們想為每個不同的 FeignClient 設置日志級別。
1.添加配置類:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
2.配置日志級別 ,logging.level + FeignClient 包的全路徑。
logging.level.com.xxx.xxxService: DEBUG
就這樣就配置完成了。重啟服務就可以看到效果。
更多配置請參考官網。
總結
Spring Cloud OpenFeign 的核心工作原理經上文探究可以非常簡單的總結為:
- 通過 @EnableFeignCleints 觸發 Spring 應用程序對 classpath 中 @FeignClient 修飾類的掃描
- 解析到 @FeignClient 修飾類后, Feign 框架通過擴展 Spring Bean Deifinition 的注冊邏輯, 最終注冊一個 FeignClientFacotoryBean 進入 Spring 容器
- Spring 容器在初始化其他用到 @FeignClient 接口的類時, 獲得的是 FeignClientFacotryBean 產生的一個代理對象 Proxy.
- 基於 java 原生的動態代理機制, 針對 Proxy 的調用, 都會被統一轉發給 Feign 框架所定義的一個 InvocationHandler , 由該 Handler 完成后續的 HTTP 轉換, 發送, 接收, 翻譯HTTP響應的工作。