前言
之前提到了:
- feign的实例bean的注册流程,大致如下:
graph TB
EnableFeignClients-->import:FeignClientsRegistrar-->registerBeanDefinitions
registerBeanDefinitions-->注册configuration的beanDefinition到context中
registerBeanDefinitions-->扫描feignClient-->在context中注册为FeignClientFactoryBean的beanDefinition
这里我们继续看看,这里的feignClientFactoryBean是如何生成bean的,并通过实际情况来看看调用链路。
装配bean
首先,factoryBean是通过getObject来获取bean的,因此来看看这里的代码。
入口
<T> T getTarget() {
//这里看到,feign的context是从容器中获取的
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
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));
}
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
复制代码
这里的feign(context,对应如下:)
protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // @formatter:off 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)); // @formatter:on configureFeign(context, builder); return builder; } 复制代码
graph LR
getObject-->getTarget-->从Spring容器中获取feignContext-->id{feign中配置的是否指定了URL}
从Spring容器中获取feignContext-->从context中获取Feign.Builder
id{feign中配置的是否指定了URL}-->负载均衡方式构造
id{feign中配置的是否指定了URL}-->非负载均衡方式构造
负载均衡方式
这里对应的就是loadBalance了:
graph LR
从context中获取client-->从context中获取target-->调用targter.target
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,是从context中获取的,这个是通过自动配置的地方注入的。
同理,这里的target,也是通过相同的方式注入的。
总的来说,这部分和前面分支上的指定URL的模式是类似的,都是获取client和target,最后调用target.target()构造最后返回的实例。
非负载均衡方式
其实这里的流程都是一样的。
graph LR
从context中获取client-->从context中获取target-->调用targter.target
这里对应如下:
//feignClientFactoryBean中
//.....
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
复制代码
可以观察到核心代码都差不多,只是这里获取的client方式有所不同。
这两个target.target的入参都有一个new HardCodedTarget<>(this.type, this.name, url),这里暂时没看出来作用,后面再说。
核心1:target.target
graph LR
id1{是否HystrixFeign}-->N-->直接调用feign.target构造返回实例
id1{是否是HystrixFeign}-->Y:配置失败的对应请求逻辑-->直接调用feign.target构造返回实例
根据下面自动配置上对应的bean,可知这里我们对应的target是HystrixTargeter,这里的Builder,是原生Feign.builder的。直接进到对应代码:
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
//【1】
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
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);
}
return feign.target(target);
}
复制代码
这里我们直接看feign.target()。
feign.target
其实这里就是一些简单的构造方法式的值注入,最终返回的是ReflectiveFeign。
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
//实例是Feign,因此对应的build如下
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);
}
复制代码
如果这里是HystrixFeign,那么build对应的如下:
public Feign build() { return build(null); } /** Configures components needed for hystrix integration. */ Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); } 复制代码
也就是说会额外设置一个invocationHandlerFactory和contract。
ReflectiveFeign.newInstance
到这里就算最后返回的值了。
- 这里算是返回了一个hardCodeTarget,其中的方法受defaultMethodHandlers代理,类受InvocationHandler代理(这里一般是FeignInvocationHandler)。
- 这里的targetToHandlersByName是前面的ParseHandlersByName,apply是封装方法并塞一个Template进去,实际返回的实例类是SynchronousMethodHandler。
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)));
}
}
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;
}
复制代码
feign调用链路
前面搞清楚了,如果要trace一个feign请求,那么首先会进入FeignInvocationHandler,因此从这里开始看调用链路。
首先方法调用都会到invoke这里:
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();
}
return dispatch.get(method).invoke(args);
}
复制代码
这里可以看到其实具体的feign调用是这里dispatch.get(method).invoke(args)
,而这里的dispatch就是上面我们提到的MethodHandler(SynchronousMethodHandler):
public Object invoke(Object[] argv) throws Throwable {
//这里的template,就是前面ParseHandlersByName的apply中设置进去的,这里就是通过设置argv来设置参数,并返回对应的template
RequestTemplate template = buildTemplateFromArgs.create(argv);
//这里的options,是在前面feign.Builder().Option中写进去的,这里的都是一些连接时间限制的数值配置
//这里会取外部传进来的参数,如果没有options的话,就用这个方法代理类上的
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;
}
}
}
复制代码
可以看到这里的核心就是这个executeAndDecode,重试的核心就是这里的循环以及retryer,我们先看executeAndDecode,这个方法直接做了请求:
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//这里会去获取请求具体的信息,此时要注意:如果我们前面的是负载均衡方式构建的,那么这里的url还是负载均衡方式的url
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//这里就是核心了,使用上面注入的client
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
复制代码
那么我们就看看这个client.execute(对应类为LoadBalancerFeignClient【2】):
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);
}
}
//这里的是自动注入的地方就带上的
private CachingSpringLoadBalancerFactory lbClientFactory;
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
复制代码
然后就到了这个executeWithLoadBalancer,这里就到Spring中的loadBalancer部分中了。
- 在这里就会最终决定,选择哪个URL进行请求。
//AbstractLoadBalancerAwareClient
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);
}
}
}
复制代码
AutoConfiguration中导入的bean
从context中获取的:
- Feign.builder
- client
- target
feign.builder
这里的builder有两个,在TraceFeignClientAutoConfiguration中进行配置的(包):
@Bean
@Scope("prototype")
@ConditionalOnClass(
name = { "com.netflix.hystrix.HystrixCommand", "feign.hystrix.HystrixFeign" })
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true")
Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) {
return SleuthHystrixFeignBuilder.builder(beanFactory);
}
@Bean
@ConditionalOnMissingBean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false",
matchIfMissing = true)
Feign.Builder feignBuilder(BeanFactory beanFactory) {
return SleuthFeignBuilder.builder(beanFactory);
}
复制代码
之所以指定为原型bean,理由也很简单: 大部分feign构造最终实例并注入都会调用这里,而这里要做不同bean之间的隔离。
这里的sleuth是Spring自带的链路追踪;这里的builder的区别其实是:上面这个是一个代理类HystrixFeign,而下面的是直接调用Feign.builder构建的实例。
在上面的案例中,我们没有配对应的参数,因此获取到的是第二个,也就是说是原生的而非是代理的。
client
这里的client对应的bean有三个,在FeignLoadBalancerAutoConfiguration中import:
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
class FeignLoadBalancerAutoConfiguration {
}
复制代码
看名字也知道:
- 第一个对应的是如果导入包中带了ApacheHttpClient,使用之;
- 第二个对应的是OKHTTP
- 第三个是默认兜底的
具体到实现上,这几个类最终返回的实例都是FeignBlockingLoadBalancerClient。
target
这里的target有两个,分别对应包路径下是否有feign.hystrix.HystrixFeign:
//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();
}
}
复制代码
集成的对应类
- sleuth
- 【1】失败处理
@TODO
- 【2】哪里判断的?