专栏系列文章: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
。