在项目上负责对接一些三方接口,鉴于之前的经验,选择使用RestTemplate来实现各种http请求,以及文件的读取。
首先写了RestTemplate的配置类来配置基础信息,代码如下:
@Configuration
@ConditionalOnClass(value = {RestTemplate.class, HttpClient.class})
public class RestTemplateConfig {
@Value("${remote.maxTotalConnect:1000}")
private int maxTotalConnect; //连接池的最大连接数1000
@Value("${remote.maxConnectPerRoute:100}")
private int maxConnectPerRoute; //单个主机的最大连接数200
@Value("${remote.connectTimeout:2000}")
private int connectTimeout; //连接超时默认2s
@Value("${remote.readTimeout:30000}")
private int readTimeout; //读取超时默认30s
@Value("${remote.readTimeout:10000}")
private int connectionRequestTimeout; // 从连接池获取连接的超时时间默认10s
//创建HTTP客户端工厂
private ClientHttpRequestFactory createFactory() {
if (this.maxTotalConnect <= 0) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(this.connectTimeout);
factory.setReadTimeout(this.readTimeout);
return factory;
}
HttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(this.maxTotalConnect)
.setMaxConnPerRoute(this.maxConnectPerRoute).setConnectionTimeToLive(30, TimeUnit.MINUTES).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(this.connectTimeout);
factory.setReadTimeout(this.readTimeout);
factory.setConnectionRequestTimeout(connectionRequestTimeout);
return factory;
}
//初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate(this.createFactory());
//换上fastjson
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
while (iterator.hasNext()) {
HttpMessageConverter<?> converter = iterator.next();
//原有的String是ISO-8859-1编码 去掉
if (converter instanceof StringHttpMessageConverter) {
iterator.remove();
}
//由于系统中默认有jackson 在转换json时自动会启用 但是我们不想使用它 可以直接移除
if (converter instanceof GsonHttpMessageConverter || converter instanceof MappingJackson2HttpMessageConverter) {
iterator.remove();
}
}
messageConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8")));
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
/** 设置支持的MediaType**/
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.APPLICATION_ATOM_XML);
mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
mediaTypes.add(MediaType.APPLICATION_PDF);
mediaTypes.add(MediaType.APPLICATION_RSS_XML);
mediaTypes.add(MediaType.APPLICATION_XHTML_XML);
mediaTypes.add(MediaType.APPLICATION_XML);
mediaTypes.add(MediaType.IMAGE_GIF);
mediaTypes.add(MediaType.IMAGE_JPEG);
mediaTypes.add(MediaType.IMAGE_PNG);
mediaTypes.add(MediaType.TEXT_EVENT_STREAM);
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.TEXT_MARKDOWN);
mediaTypes.add(MediaType.TEXT_PLAIN);
mediaTypes.add(MediaType.TEXT_XML);
converter.setSupportedMediaTypes(mediaTypes);
// 设置默认的字符集
converter.setDefaultCharset(Charset.forName("UTF-8"));
// 设置默认的时间格式
JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat);
// 设置序列化方式
fastJsonConfig.setSerializerFeatures(
//List字段如果为null,输出为[],而非null
SerializerFeature.WriteNullListAsEmpty,
//是否输出值为null的字段
SerializerFeature.WriteMapNullValue,
//字符类型字段如果为null,输出为”“,而非null
SerializerFeature.WriteNullStringAsEmpty,
//消除对同一对象循环引用的问题
SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(fastJsonConfig);
messageConverters.add(converter);
return restTemplate;
}
}
然后在项目中用@Resource RestTemplate 使用就可以了;代码完成后打包启用一切正常。在测试环境运行一周后突然开始报错,测试反馈系统一直timed out,整个业务流程被阻断了
查看日志,确如测试同事所说,当天调用该方法的请求都出现了这个问题。这就尴尬了,之前也这么写的,为啥现在报错了,而且前一天还是正常的。一直没想通,改用httpClient方式,发现还是timed out。再看代码,是系统要连续读取两张照片,第一张读取时是没问题的,在读取第二张时,系统明显感觉发送完请求就假死了,一直到连接超时。
鉴于图片用两种读取方式都能读到第一张,所以想着用每种方式读取一张,看能不能完成本次需求,代码修改后,再次测试,系统正常跑完流程,未再报错。但是为什么用RestTemplate连续读取两次报错,一直不理解。直到查资料RestTemplate还有个connectionRequestTimeout时间设置。源码如下:
用翻译软件翻译结果如下:
设置请求连接时使用的超时(以毫秒为单位) 使用底层的{@link RequestConfig}从连接管理器获取。 超时值0指定无限超时。 其他属性可以通过指定 {@link RequestConfig}在自定义{@link HttpClient}上的实例。 请求连接的超时值,以毫秒为单位。
原来是RestTemplate,连接池关闭获取不到连接就报readtimeout异常,随后在RestTemplateConfiguration设置上connectionRequestTimeout时间10s,再次测试OK。与通过两种不同方式读取结果一致,都能完成业务正常流水。至此问题解决!