spring for android是一个android平台下的网络框架,由大名鼎鼎的spring项目组开发。spring for android对于处理rest网络请求特别方便,这是我使用spring for android的主要原因。使用过程中,可能发现spring for android这套框架不好设置超时,有时甚至设置了也无效。如果使用了aa注解框架,设置超时更麻烦点。下面从源码角度解读这些问题。

常用解决方法

常用解决方法有两种

  1. 获取requestFactory,然后设置超时。
  2. 继承一个已有的requestFactory,构造RestTemplate时传入自己的requestfactory

第一种方式可以封装成如下代码:

public static void configTimeout(RestTemplate template, int readTimeout,
int connectTimeout) {
boolean updateReadTimeout = readTimeout >= 0;
boolean updateConnectTimeout = connectTimeout >= 0;

if (template == null || (!updateReadTimeout && !updateReadTimeout))
return;
ClientHttpRequestFactory factory = template.getRequestFactory();
if (factory instanceof SimpleClientHttpRequestFactory) {
SimpleClientHttpRequestFactory simple = (SimpleClientHttpRequestFactory) factory;
if (updateReadTimeout)
simple.setReadTimeout(readTimeout);
if (updateConnectTimeout)
simple.setConnectTimeout(connectTimeout);
} else if (factory instanceof HttpComponentsClientHttpRequestFactory) {
HttpComponentsClientHttpRequestFactory client = (HttpComponentsClientHttpRequestFactory) factory;
if (updateReadTimeout)
client.setReadTimeout(readTimeout);
if (updateConnectTimeout)
client.setConnectTimeout(connectTimeout);
}
}

如果使用aa框架,这样配置更麻烦,每次都需要获取restTemplate。如果使用aa框架,推荐使用第二种方式。
第二种方式,首先需要自定义一个类,继承一个RequestFactory,构造时设置超时。

public class MyHttpFactory  extends SimpleClientHttpRequestFactory{

public MyHttpFactory(){
super();
setConnectTimeout(7*1000);
setReadTimeout(7*1000);

}

然后构造restTemplate

public void test(){
RestTemplate template = new RestTemplate(new MyHttpFactory());
// 网络请求
}

这种方式主要是在使用aa框架下使用,aa的rest接口可以传入requestFactory,我们自定义的factory就能用了,比如:

@Rest(converters = {FormHttpMessageConverter.class, GsonHttpMessageConverter.class,
StringHttpMessageConverter.class}, rootUrl = HttpURL.ROOT_URL + "tests/",
requestFactory = MyHttpFactory.class)
public interface TestApi extends RestClientSupport {

@Post("?name={name}")
public void test(String name);
}

设置超时无效原因

在使用上述第一种解决方案时,添加了拦截器之后,发现设置超时无效,什么原因呢,可以从spring for android源码看看。
RestTemplate继承InterceptingHttpAccessor类,在InterceptingHttpAccessor类里实现了一个方法getRequestFactory,这个方法作用就是获取RequestFactory,方法实现如下

@Override
public ClientHttpRequestFactory getRequestFactory() {
ClientHttpRequestFactory delegate = super.getRequestFactory();
if (!CollectionUtils.isEmpty(getInterceptors())) {
return new InterceptingClientHttpRequestFactory(delegate, getInterceptors());
}
else {
return delegate;
}
}

可以看到,如果RestTemplate有拦截器,则返回一个代理ReqeustFactoryInterceptingClientHttpRequestFactory,而InterceptingClientHttpRequestFactory并没有提显式提供什么方法获取真实的RequestTemplate

超时无效的解决方法

从上例中方法我们可以看出解决方法,那就是先设置拦截器为空,然后设置超时,最后重新设置回拦截器。具体代码改动如下:

public static void configTimeout(RestTemplate template, int readTimeout,
int connectTimeout) {
boolean updateReadTimeout = readTimeout >= 0;
boolean updateConnectTimeout = connectTimeout >= 0;

if (template == null || (!updateReadTimeout && !updateReadTimeout))
return;
//这里是为了解决spring requestfactory不能获取到真实factory的问题,先设置过滤器为空,设置后超时后
//再重新设置回去
List<ClientHttpRequestInterceptor> interceptors = template.getInterceptors();
template.setInterceptors(null);
try {

ClientHttpRequestFactory factory = template.getRequestFactory();
// InterceptingClientHttpRequestFactory interceptingClientHttpRequestFactory;


if (factory instanceof SimpleClientHttpRequestFactory) {
SimpleClientHttpRequestFactory simple = (SimpleClientHttpRequestFactory) factory;
if (updateReadTimeout)
simple.setReadTimeout(readTimeout);
if (updateConnectTimeout)
simple.setConnectTimeout(connectTimeout);
} else if (factory instanceof HttpComponentsClientHttpRequestFactory) {
HttpComponentsClientHttpRequestFactory client = (HttpComponentsClientHttpRequestFactory) factory;
if (updateReadTimeout)
client.setReadTimeout(readTimeout);
if (updateConnectTimeout)
client.setConnectTimeout(connectTimeout);
}

} finally {
//重新设置回去
template.setInterceptors(interceptors);
}


}

如果你觉得这种方式好麻烦,那就来个简单暴力点的,通过无所不能的反射来设置超时

/**
* 配置RestTemplate超时时间,通过反射强制获取factry设置属性
*
* @param restTemplate
* @param readTimeout 读取超时,如果小于等于0,则不设置
* @param connectTimeout 连接超时时间,如果小于等于0,则不设置
*/
public static void configTimeoutByReflect(RestTemplate restTemplate, int readTimeout, int connectTimeout) {
ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
boolean updateReadTimeout = readTimeout >= 0;
boolean updateConnectTimeout = connectTimeout >= 0;
if (factory instanceof InterceptingClientHttpRequestFactory) {
try {
factory = (ClientHttpRequestFactory) ReflectUtils.getFieledValue(factory, "requestFactory");
if(factory == null) return;
} catch (IllegalAccessException e) {
e.printStackTrace();
}

}


if (factory instanceof SimpleClientHttpRequestFactory) {
SimpleClientHttpRequestFactory simple = (SimpleClientHttpRequestFactory) factory;
if (updateReadTimeout)
simple.setReadTimeout(readTimeout);
if (updateConnectTimeout)
simple.setConnectTimeout(connectTimeout);
} else if (factory instanceof HttpComponentsClientHttpRequestFactory) {
HttpComponentsClientHttpRequestFactory client = (HttpComponentsClientHttpRequestFactory) factory;
if (updateReadTimeout)
client.setReadTimeout(readTimeout);
if (updateConnectTimeout)
client.setConnectTimeout(connectTimeout);
}


}

ReflectUtils代码如下:

package com.dreamliner.secretchat.utils;

import java.lang.reflect.Field;

/**
* Created by Administrator on 2015/5/12.
*/
public class ReflectUtils {


/**
* Gets fieled value.
*获取到Filed的值
* @param obj the obj
* @param fieldName the field name
* @return the fieled value
* @throws IllegalAccessException the illegal access exception
*/
public static Object getFieledValue(Object obj, String fieldName)
throws IllegalAccessException {
Field field = getFieldByRecursion(obj.getClass(), fieldName);
if (field != null) {
field.setAccessible(true);
return field.get(obj);
}
return null;

}


/**
* Sets field value.
*
* @param obj the obj
* @param fildName the fild name
* @param fieldValue the field value
* @return the field value
*/
public static boolean setFieldValue(Object obj, String fildName,
Object fieldValue) {
Field field = getFieldByRecursion(obj.getClass(), fildName);
boolean result = false;
if (field != null) {
field.setAccessible(true);
try {
field.set(obj, fieldValue);

result = true;
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}

}
return result;
}

/**
* Gets field by recursion.
*
* @param classes the classes
* @param fildName the fild name
* @return the field by recursion
*/
// 用递归实现
public static Field getFieldByRecursion(Class<?> classes, String fildName) {
if (classes == null)
return null;
Field field = getFieldInCurrentClass(classes, fildName);

return field == null ? getFieldByRecursion(classes.getSuperclass(), fildName)
: field;

}

/**
* Gets field by loop.
*
* @param classes the classes
* @param fildName the fild name
* @return the field by loop
*/
// 用循环实现
public static Field getFieldByLoop(Class<?> classes, String fildName) {
boolean havaField = false;
Field field = null;
Class<?> currentClass = classes;
while (!havaField) {
if (currentClass == null)
break;

field = getFieldInCurrentClass(currentClass, fildName);
if (field != null)
break;
currentClass = currentClass.getSuperclass();

}

return field;

}

/**
* Gets field in current class.
*
* @param classes the classes
* @param fildName the fild name
* @return the field in current class
*/
public static Field getFieldInCurrentClass(Class<?> classes, String fildName) {
Field[] declaredFields = classes.getDeclaredFields();
for (Field field : declaredFields) {
if (field.getName().equals(fildName)) {
return field;
}
}
return null;

}


}