前 言
突然!频繁收到一个老系统的告警通知,全是调用第三方接口超时的报错。排查后发现,竟是RestTemplate配置惹的祸!明明自定义了600秒的超时,实际却10秒就超时了。今天就把整个踩坑、排坑过程跟大家捋一捋,帮大家避避坑!

填坑过程
一开始我也懵了:明明在restTemplateSimple这个Bean里设置了600秒超时,10分钟的接口调用还超时?

排查接口调用日志,发现调用第三方接口10秒后就超时了,跟restTemplateSimple配置的超时时间完全不符

检查Bean注入情况,才发现Service里用@Autowired注入的是restTemplate,而不是我们配置的restTemplateSimpe

这就尴尬了——Spring容器里同时存在两个RestTemplate实例,我们注错了对象!
为什么默认是10秒
你可能好奇,那10秒超时是哪来的?这里有个坑:如果你用SimpleClientHttpRequestFactory且没明确设置超时,或者通过RestTemplateBuilder配置但没指定超时,Spring Boot会有默认行为。在某些版本或配置下,这个默认值可能就是10秒。

让我们使用以下代码,验证下默认超时时间,发现输出的超时时间确实是10秒。
/**
* 验证restTemplate的默认超时时间
*/
@PostConstruct
public void checkRestTemplateTimeout() {
try {
ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
log.info("RestTemplate RequestFactory 类型: {}", factory.getClass().getName());
// 如果是 InterceptingClientHttpRequestFactory,需要获取内部的真实 factory
if (factory instanceof InterceptingClientHttpRequestFactory) {
ClientHttpRequestFactory realFactory = getInternalFactory((InterceptingClientHttpRequestFactory) factory);
log.info("内部真实 RequestFactory 类型: {}", realFactory.getClass().getName());
if (realFactory instanceof SimpleClientHttpRequestFactory) {
SimpleClientHttpRequestFactory simpleFactory = (SimpleClientHttpRequestFactory) realFactory;
Object connectTimeout = getFieldValue(simpleFactory, "connectTimeout");
Object readTimeout = getFieldValue(simpleFactory, "readTimeout");
log.info("connectTimeout: {} ms", connectTimeout);
log.info("readTimeout: {} ms", readTimeout);
}
} else if (factory instanceof SimpleClientHttpRequestFactory) {
SimpleClientHttpRequestFactory simpleFactory = (SimpleClientHttpRequestFactory) factory;
Object connectTimeout = getFieldValue(simpleFactory, "connectTimeout");
Object readTimeout = getFieldValue(simpleFactory, "readTimeout");
log.info("connectTimeout: {} ms", connectTimeout);
log.info("readTimeout: {} ms", readTimeout);
}
} catch (Exception e) {
log.error("获取 RestTemplate 超时配置失败", e);
}
}
/**
* 从 InterceptingClientHttpRequestFactory 中获取内部的真实 ClientHttpRequestFactory
*/
private ClientHttpRequestFactory getInternalFactory(InterceptingClientHttpRequestFactory interceptingFactory) {
try {
Field field = AbstractClientHttpRequestFactoryWrapper.class.getDeclaredField("requestFactory");
field.setAccessible(true);
return (ClientHttpRequestFactory) field.get(interceptingFactory);
} catch (Exception e) {
log.error("获取内部 factory 失败", e);
return null;
}
}
/**
* 通过反射获取对象的私有字段值
*/
private Object getFieldValue(Object obj, String fieldName) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
log.error("获取字段值失败: {}", fieldName, e);
return null;
}
}
解决方案:使用 @Qualifier 注入指定的 Bean
@Autowired
@Qualifier("restTemplateSimple") // 指定注入你配置的 Bean
private RestTemplate restTemplate;
经验总结与最佳实践
通过这次排查,我们总结出以下经验:
-
显式配置超时时间:永远不要依赖默认超时设置,因为不同环境下的默认行为可能不同
-
Bean命名要规范:在Spring项目中,Bean的命名和注入应该保持一致,避免歧义
-
超时配置验证:一定要验证超时配置是否生效,可以编写单元测试进行验证

你是否在项目中也遇到过类似的坑?欢迎在评论区分享你的经历和解决方案!

3132

被折叠的 条评论
为什么被折叠?



