專欄系列文章:SpringCloud系列專欄java
系列文章:web
SpringCloud 源碼系列(1)— 註冊中心Eureka 之 啓動初始化spring
SpringCloud 源碼系列(2)— 註冊中心Eureka 之 服務註冊、續約apache
SpringCloud 源碼系列(3)— 註冊中心Eureka 之 抓取註冊表緩存
SpringCloud 源碼系列(4)— 註冊中心Eureka 之 服務下線、故障、自我保護機制markdown
SpringCloud 源碼系列(5)— 註冊中心Eureka 之 EurekaServer集羣網絡
SpringCloud 源碼系列(6)— 註冊中心Eureka 之 總結篇mvc
SpringCloud 源碼系列(7)— 負載均衡Ribbon 之 RestTemplateapp
SpringCloud 源碼系列(8)— 負載均衡Ribbon 之 核心原理負載均衡
SpringCloud 源碼系列(9)— 負載均衡Ribbon 之 核心組件與配置
SpringCloud 源碼系列(10)— 負載均衡Ribbon 之 HTTP客戶端組件
SpringCloud 源碼系列(11)— 負載均衡Ribbon 之 重試與總結篇
SpringCloud 源碼系列(12)— 服務調用Feign 之 基礎使用篇
SpringCloud 源碼系列(13)— 服務調用Feign 之 掃描@FeignClient註解接口
從前文中已經分析出 FeignClientFactoryBean
這個組件就是生成 FeignClient 接口動態代理的組件。
FeignClientFactoryBean 實現了 FactoryBean
接口,當一個Bean實現了 FactoryBean 接口後,Spring 會先實例化這個工廠,而後在須要的時候調用 getObject()
建立真正的Bean。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
}
複製代碼
FeignClientFactoryBean 實現了 getObject()
方法,它又調用了 getTarget()
方法,getTarget()
最後就建立了 FeignClient 接口的動態代理對象。
建立動態代理對象的主要流程以下:
FeignContext
,FeignContext 跟 Ribbon 中 SpringClientFactory
是相似的,能夠獲取到每一個服務的上下文。由於每一個服務都有本身的配置、Encoder、Decoder 組件等,因此能夠從 FeignContext 中獲取到當前服務的組件。Feign.Builder
,這個 Feign.Builder 就是最終用來建立動態代理對象的構造器。url
,就會經過服務名稱構造帶服務名的url地址,跟 RestTemplate 相似,最終確定就是走負載均衡的請求;若是配置了 url,就是直接調用這個地址。 Client
,若是配置了 url,就是獲取 client 裏的代理對象
,並設置到 builder 中;不然就直接將 Client 設置到 builder。也就是說根據 url 判斷是否使用負載均衡的 Client。target()
方法來構造動態代理對象,target 傳入的參數包括當前的 FeignClientFactoryBean 對象、Feign.Builder、FeignContext,以及封裝的 HardCodedTarget
對象。// 獲取 FeignClient 代理對象的入口
@Override
public Object getObject() throws Exception {
return getTarget();
}
/** * 建立一個 FeignClient 接口的代理對象,T 就是 @FeignClient 註解的接口類型 * * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context information */
<T> T getTarget() {
// Feign 上下文
FeignContext context = applicationContext.getBean(FeignContext.class);
// Feign 構造器
Feign.Builder builder = feign(context);
// 若是沒有直接配置 url,就走負載均衡請求
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
// 帶服務名的地址 => http://demo-consumer
url += cleanPath();
// 返回的類型確定是具有負載均衡能力的;HardCodedTarget => 硬編碼的 Target
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
// 若是配置了 url,就直接請求 url 地址
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
// Client => Feign 發起 HTTP 調用的核心組件
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// 獲得的是代理對象,就是原生的 Client.Default
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// 獲得的是代理對象,就是原生的 Client.Default
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
// targeter 建立動態代理對象
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
複製代碼
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
// 獲取 Client
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// Targeter => HystrixTargeter
Targeter targeter = get(context, Targeter.class);
// targeter 建立動態代理對象
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?");
}
複製代碼
feign()
方法返回了 Feign.Builder
,它也是從 FeignContext 中獲取的,這個方法最重要的是設置了 Logger、Encoder、Decoder、Contract
,並讀取配置文件中 feign.client.*
相關的配置。FeignClientsConfiguration 中配置了這幾個接口的默認實現類,咱們也能夠自定義這幾個實現類。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// 咱們能夠定製 Logger、Encoder、Decoder、Contract
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));
// 讀取配置文件中 feign.client.* 的配置來配置 Feign
configureFeign(context, builder);
return builder;
}
複製代碼
Feign.Builder
的默認實現是什麼呢?從 FeignClientsConfiguration 中能夠知道,默認狀況下就是 Feign.Builder
,若是啓用了 feign.hystrix.enabled
,那默認實現就是 HystrixFeign.Builder
。
那 Feign.Builder 和 HystrixFeign.Build 有什麼區別呢?對比下不難發現,主要區別就是建立動態代理的實現類 InvocationHandler
是不一樣的,在啓用 hystrix 的狀況下,會涉及到熔斷、降級等,HystrixFeign.Build 也會設置 @FeignClient 配置的 fallback、fallbackFactory
降級配置類。這塊等後面分析 hystrix 源碼時再來看。如今只須要知道,feign 沒有啓用 hystrix,@FeignClient 配置的 fallback、fallbackFactory 降級回調是不生效的。
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
// 從不重試
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
// 默認爲 Feign.Builder
return Feign.builder().retryer(retryer);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
// 引入了 hystrix 而且,feign.hystrix.enabled = true
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
// feign 啓用 hystrix 後,Feign.Builder 就是 HystrixFeign.Builder
return HystrixFeign.builder();
}
}
}
複製代碼
configureFeign()
方法就是配置 Feign.Builder 的,從這個方法能夠驗證基礎篇文章中 feign 配置生效的優先級。
Feign 有三塊配置,一個是能夠經過 Configuration 的方式配置,而後設置到 @FeignClient 的 configuration
參數;而後是全局的 feign.client.default
默認配置,以及服務特定的配置 feign.client.<clientName>
。
從 configureFeign()
方法能夠看出,默認狀況下,優先級最低的是代碼配置,其次是默認配置,最高優先級的是服務特定的配置
。
若是想使代碼配置優先級高於文件中的配置,能夠設置 feign.client.defalut-to-properties=false
來改變 Feign 配置生效的優先級。
protected void configureFeign(FeignContext context, Feign.Builder builder) {
// 配置文件中 feign.client.* 客戶端配置
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) {
// defaultToProperties:優先使用配置文件中的配置
if (properties.isDefaultToProperties()) {
// 最低優先級:使用代碼中的 Configuration 配置
configureUsingConfiguration(context, builder);
// 次優先級:使用 feign.client.default 默認配置
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
// 高優先級:使用 feign.client.<clientName> 定義的配置
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
// 優先使用Java代碼的配置
else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
複製代碼
Client
是 feign-core
中的組件,它只有一個接口 execute
,這個接口就是調用 Request
的 url,而後將返回接口封裝到 Response
中。
public interface Client {
/** * Executes a request against its {@link Request#url() url} and returns a response. * * @param request safe to replay. * @param options options to apply to this request. * @return connected response, {@link Response.Body} is absent or unread. * @throws IOException on a network error connecting to {@link Request#url()}. */
Response execute(Request request, Options options) throws IOException;
}
複製代碼
Client 有以下的一些實現類:
Client 的自動化配置類是 FeignRibbonClientAutoConfiguration
,FeignRibbonClientAutoConfiguration 導入了 HttpClient、OkHttp 以及默認的 Feign 負載均衡配置類。
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled", matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
}
複製代碼
從 HttpClientFeignLoadBalancedConfiguration
的配置能夠看出,要啓用 apache httpclient,需設置 feign.httpclient.enabled=true
(默認爲 true),而且須要加入了 feign-httpclient
的依賴(ApacheHttpClient)
啓用 apache httpclient 後,LoadBalancerFeignClient
的代理對象就是 feign-httpclient 中的 ApacheHttpClient
。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
複製代碼
從 OkHttpFeignLoadBalancedConfiguration
的配置能夠看出,要啓用 okhttp,需設置 feign.okhttp.enabled=true
,且須要引入 feign-okhttp
的依賴(OkHttpClient)。
啓用 okhttp 後,LoadBalancerFeignClient
的代理對象就是 feign-okhttp 的 OkHttpClient
。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
複製代碼
沒有引入 feign-httpclient
或者 feign-okhttp
,就會走默認的 DefaultFeignLoadBalancedConfiguration
。而默認的代理對象 Client.Default
其實就是使用 HttpURLConnection
發起 HTTP 調用。
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
}
}
複製代碼
能夠看出,三個配置類建立的 Client 對象都是 LoadBalancerFeignClient
,也就是支持負載均衡的請求。只是代理類不一樣,也就是最終發起 HTTP 調用的組件是不一樣的,默認配置下的代理類是 Client.Default,底層就是 HttpURLConnection。
這塊其實跟分析 Ribbon 源碼時,RestTemplate 的負載均衡是相似的。
Targeter
接口只有一個接口方法,就是經過 target()
方法獲取動態代理對象。Targeter 有 DefaultTargeter、HystrixTargeter
兩個實現類,
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target);
}
複製代碼
在 FeignAutoConfiguration
配置類中可看到,只要引入了 HystrixFeign
,Targeter 的默認實現就是 HystrixTargeter
。
HystrixTargeter 一看就是用來整合 feign 和 hystrix 的,使 feign 調用能夠實現熔斷、限流、降級。
public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
複製代碼
能夠看到 HystrixTargeter 和 DefaultTargeter 的區別就在於 HystrixTargeter 會向 Feign.Builder
設置降級回調處理類,這樣 feign 調用觸發熔斷、降級時,就能夠進入回調類處理。
它們本質上最終來講都是調用 Feign.Builder 的 target()
方法建立動態代理對象。
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
// 非 HystrixFeign.Builder 類型,就直接調用 target 方法
return feign.target(target);
}
// Feign 啓用了 hystrix 後,就會向 HystrixFeign.Builder 設置回調類或回調工廠
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId();
Class<?> fallback = factory.getFallback();
// 設置回調類
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
// 設置回調工廠類
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
// 調用 Feign.Builder 建立動態代理
return feign.target(target);
}
}
複製代碼
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.Builder 的默認實現就是 Feign.Builder,HystrixTargeter 中調用了 Feign.Builder 的 target
方法來建立動態代理。
build()
方法構建出 Feign
,而後調用 Feign 的 newInstance
建立動態代理對象。build()
方法中首先讀取配置的 Client、Retryer、Logger、Contract、Encoder、Decoder
等對象。InvocationHandlerFactory
,默認就是 InvocationHandlerFactory.Default
,這是 feign 提供的一個工廠類來建立代理對象 InvocationHandler
。SynchronousMethodHandler.Factory
,它就是用來將接口方法封裝成一個方法執行器 MethodHandler
,默認實現類是 SynchronousMethodHandler
。ParseHandlersByName
,可想而知,這就是用來處理接口中的 springmvc 註解的,將 REST 接口解析生成 MethodHandler。public <T> T target(Target<T> target) {
return build().newInstance(target);
}
// 構建 Feign
public Feign build() {
// Feign Http調用客戶端,默認爲 Client.Default
Client client = Capability.enrich(this.client, capabilities);
// 重試器,默認是重不重試
Retryer retryer = Capability.enrich(this.retryer, capabilities);
// Feign 請求攔截器,能夠對 Feign 請求模板RequestTemplate作一些定製化處理
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
// 日誌組件,默認爲 Slf4jLogger
Logger logger = Capability.enrich(this.logger, capabilities);
// 接口協議組件,默認爲 SpringMvcContract
Contract contract = Capability.enrich(this.contract, capabilities);
// 配置類
Options options = Capability.enrich(this.options, capabilities);
// 編碼器
Encoder encoder = Capability.enrich(this.encoder, capabilities);
// 解碼器
Decoder decoder = Capability.enrich(this.decoder, capabilities);
// 建立 InvocationHandler 的工廠類
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
// 接口方法處理器工廠
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
// 解析 springmvc 註解
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
// ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
複製代碼
InvocationHandlerFactory
包含一個 create
接口方法,默認實現是 InvocationHandlerFactory.Default,返回的 InvocationHandler 類型是 ReflectiveFeign.FeignInvocationHandler
。
package feign;
public interface InvocationHandlerFactory {
// 建立動態代理
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
// 方法處理器
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
複製代碼
接着看 ReflectiveFeign 的 newInstance()
方法:
newInstance
的參數 target 就是前面封裝的 Target.HardCodedTarget
,它封裝了客戶端的類型、url
等屬性。ParseHandlersByName
將 FeignClient 接口中的接口轉換成 MethodHandler,實際類型就是 SynchronousMethodHandler
,這個細節就不在看了。InvocationHandlerFactory
建立 InvocationHandler 代理對象,也就是 ReflectiveFeign.FeignInvocationHandler
,調用動態代理對象的方法,最終都會進入到這個執行處理器裏面。Proxy
建立了 FeignClient 的動態代理對象,這個動態代理的類型就是 @FeignClient 註解的接口的類型。最後被注入到 IoC 容器後,就能夠在代碼中注入本身編寫的 FeignClient 客戶端組件了。最終就是經過 Proxy
建立一個實現了 FeignClient 接口的動態代理,而後全部接口方法的調用都會被 FeignInvocationHandler
攔截處理。
public <T> T newInstance(Target<T> target) {
// 使用 ParseHandlersByName 將 FeignClient 接口中的接口轉換成 MethodHandler,springmvc 註解由 Contract 組件處理
// MethodHandler => SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// 轉換成 Method - MethodHandler 映射
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)));
}
}
// 用 SynchronousMethodHandler.Factory 建立 SynchronousMethodHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 用 Proxy 建立動態代理,動態代理對象就是 SynchronousMethodHandler
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
複製代碼
下面用一張圖來總結下生成 FeignClient 動態代理的流程:
@EnableFeignClients
導入的註冊器 FeignClientsRegistrar
會掃描 @FeignClient
註解的接口,並生成 FeingClientFactoryBean
的 BeanDefinition
註冊到容器中。最後會調用 FeingClientFactoryBean 的 getObject
方法來獲取接口的動態代理對象。FeignContext
,它其實就是每一個客戶端的容器,相似於一個 Map 結構,緩存了客戶端與容器間的關係,後續大部分組件都是從 FeignContext 中獲取。Feign.Builder
,並配置 Feign.Builder,配置來源有多個地方,優先級最高的是 application.yml 中的配置生效;也能夠配置 feign.client.default-to-properties=false
設置Java代碼配置爲高優先級。Targeter
,調用它的 target
方法來獲取動態代理。build()
方法構建了 ReflectiveFeign
:
InvocationHandlerFactory
,用於建立 InvocationHandler
SynchronousMethodHandler.Factory
,接着建立了方法解析器 ParseHandlersByName
ReflectiveFeign
newInstance
方法反射建立接口的動態代理:
SynchronousMethodHandler
InvocationHandler
(ReflectiveFeign.FeignInvocationHandler)Proxy
建立動態代理對象,對象的類型就是接口的類型,代理對象就是 ReflectiveFeign.FeignInvocationHandler
。