Fegin源码分析
思考Feign要做的事情
- 参数的解析和组装
- 针对指定的FeginClient生成代理对象
- 针对FeginClient中的方法描述进行解析
- 组装成一个request对象,发起请求
Bean的动态装载
在了解Fegin的源码之前先了解一下Spring Boot中Bean的动态装载,有两种方式
- ImportSelector 可以实现Bean的批量动态装载
- ImportBeanDefinitionRegistrar 可以实现Bean的动态装载
ImportBeanDefinitionRegistrar
这是一个接口,通过BeanDefinitionRegistry注册Bean对象,接下来演示Bean的动态装载
HelloService
定义一个HelloService的Bean
@Data
public class HelloService{
private String name;
}
GxImportBeanDefinitionRegistrar
自定义ImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar接口
public class GxImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
EnableGxRegistrara
添加EnableGxRegistrara注解,并且用@Import(GxImportBeanDefinitionRegistrar.class)导入刚刚定义的ImportBeanDefinitionRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GxImportBeanDefinitionRegistrar.class)
public @interface EnableGxRegistrara {
}
SpringCloudUserServiceApplication
在我们的启动类上添加 @EnableGxRegistrara 注解,然后就可以从spring容器中获取我们刚刚注册的HelloService实例了.
@EnableGxRegistrara
@EnableFeignClients(basePackages = "com.gxedu.example.clients")
@SpringBootApplication
public class SpringCloudUserServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext contxt=SpringApplication.run(SpringCloudUserServiceApplication.class, args);
System.out.println(contxt.getBean(HelloService.class));
}
}
这就是动态装载的功能实现,它相比于@Configuration配置注入,会多了很多的灵活性。
源码解析
@EnableFeignClients(basePackages = “com.gxedu.example.clients”)
@EnableGxRegistrara
@EnableFeignClients(basePackages = "com.gxedu.example.clients")
@SpringBootApplication
public class SpringCloudUserServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext contxt=SpringApplication.run(SpringCloudUserServiceApplication.class, args);
System.out.println(contxt.getBean(HelloService.class));
}
}
这个注解的声明如下,它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来 去看一下FeignClientsRegistrar的定义
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
FeignClientsRegistrar.registerBeanDefinitions()
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口, Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。 它的作用类似于ImportSelector。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
}
- registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有 @EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
- registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的 BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中.
registerFeignClients(metadata, registry);
这个方法主要是扫描类路径下所有的 @FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
...........
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//构建一个BeanDefinitionBuilder,参数传递FactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,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() 逻辑返回的示例。
BeanDefinitionBuilder.genericBeanDefinition()
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一 个代理对象。
FeignClientFactoryBean.getObject()
getObject调用的是getTarget()方法,它从applicationContext取出FeignContext,FeignContext继承了 NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//实例化Feign上下文对象FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//根据上下文对象构建Encoder,Decoder,LoadBalance,返回一个Builder对象
Feign.Builder builder = feign(context);
//如果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));
}
}
FeignContext注册到容器是在FeignAutoConfiguration上完成
@ConditionalOnClass({Feign.class})
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
@Import({DefaultGzipDecoderConfiguration.class})
public class FeignAutoConfiguration {
@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()方法。
FeignContext
如何隔离bean,奥秘就在FeignContext.class
这个类,首先看获取FeignClient的源码中有这样一段:
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
/**
* @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() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
}
feign(context);
我们可以看到在build的时候从容器中拿到了一个FeignContext的实例,再看生成builder的方法:
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;
}
大家可以跟进去get(context, Encoder.class)
,可以看到构造builder时,是从这个FeignContext中获取对应的ecoder
实例的,那么FeignContext
到底是什么呢?
protected <T> T get(FeignContext context, Class<T> type) {
//调用getInstance方法
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
public <T> T getInstance(String name, Class<T> type) {
//调用getContext(),获取applicationContext
AnnotationConfigApplicationContext context = this.getContext(name);
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0 ? context.getBean(type) : null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
//先从缓存中查找,如果有直接返回,
if (!this.contexts.containsKey(name)) {
Map var2 = this.contexts;
synchronized(this.contexts) {
if (!this.contexts.containsKey(name)) {
//没有的话,重新构建一个,调用createContext()方法
this.contexts.put(name, this.createContext(name));
}
}
}
return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
从FeignContext
的源码中可以看到,它是继承自NamedContextFactory
的一个类,这个类主要的两个属性如下:
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private ApplicationContext parent;
contexts是一个map,key是feign的name,value是一个AnnotationConfigApplicationContext,从protected AnnotationConfigApplicationContext createContext(String name)
的源码中可以看出,每个context会去解析配置在FeignClient中的configuration
类,将类中定义的@bean
注册到当前的AnnotationConfigApplicationContext
里,同时将容器的context设置为自己的父context:
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
//注册配置类中的bean
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
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) {
//设置自己的父context为容器的context
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
然后在生成FeignClient的时候,获取作用在该Client上的组件时,调用如下方法:
public <T> T getInstance(String name, Class<T> type) {
//获取该Feign对应的context
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
//从自己的context中获取对应的组件,会依次往上从父context中寻找
return context.getBean(type);
}
return null;
}
至此,就搞清了Feign是如何隔离开不同FeignClient的配置。
loadBalance
生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端
Client client = (Client)this.getOptional(context, Client.class); 从上下文中获取一个 Client,默认是LoadBalancerFeignClient。
它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的
@AutoConfigureBefore({FeignAutoConfiguration.class})
@EnableConfigurationProperties({FeignHttpClientProperties.class})
@Import({HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class})
public class FeignRibbonClientAutoConfiguration {
}
@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);
}
}
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?");
}
DefaultTarget.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);
}
}
ReflectiveFeign.newInstance
public <T> T newInstance(Target<T> target) {
//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
Method[] var5 = target.type().getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method method = var5[var7];
if (method.getDeclaringClass() != Object.class) {
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)));
}
}
}
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
InvocationHandler handler = this.factory.create(target, methodToHandler);
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
Iterator var12 = defaultMethodHandlers.iterator();
while(var12.hasNext()) {
DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
接口定义的参数解析
根据FeignClient接口的描述解析出对应的请求数据。
targetToHandlersByName.apply(target)
根据Contract协议规则,解析接口类的注解信息,解析成内部表现: targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配 置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个 的map,放入InvocationHandler的实现FeignInvocationHandler中。
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap();
Iterator var4 = metadata.iterator();
while(var4.hasNext()) {
MethodMetadata md = (MethodMetadata)var4.next();
Object buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
} else {
buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md, this.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(), this.factory.create(target, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder));
}
}
return result;
}
this.factory.create(target, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder)
public MethodHandler create(Target<?> target,
MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
Options options,
Decoder decoder,
ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, decode404, closeAfterDecode, propagationPolicy);
}
factory.create(target, methodToHandler);
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
SpringMvcContract
当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求 协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式 进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务。
OpenFeign调用过程
在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的 对象。 那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是 一个动态代理的实现。
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@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();
}
return dispatch.get(method).invoke(args);
}
}
而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。 this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。 这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。
dispatch.get(method).invoke(args);
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;
}
}
}
动态生成Request
executeAndDecode
经过上述的代码,我们已经将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);
//获取返回结果
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} 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
默认采用JDK的 HttpURLConnection 发起远程调用。
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
int status = connection.getResponseCode();
String reason = connection.getResponseMessage();
if (status < 0) {
throw new IOException(format("Invalid status(%s) executing %s %s", status,
connection.getRequestMethod(), connection.getURL()));
}
Map<String, Collection<String>> headers = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
// response message
if (field.getKey() != null) {
headers.put(field.getKey(), field.getValue());
}
}
Integer length = connection.getContentLength();
if (length == -1) {
length = null;
}
InputStream stream;
if (status >= 400) {
stream = connection.getErrorStream();
} else {
stream = connection.getInputStream();
}
return Response.builder()
.status(status)
.reason(reason)
.headers(headers)
.request(request)
.body(stream, length)
.build();
}