1、概述
java.lang.RuntimeException: java.io.IOException: Attempted read from closed stream 错误通常表示程序试图从一个已经关闭的流中读取数据。在Java中,一旦一个流被关闭,任何尝试从该流中读取数据的操作都会抛出IOException。
最近使用PowerJob添加工作流管理, 往powerjob_workflow_node_info、powerjob_workflow_info表两个表插入数据,调的是engin-service。报java.io.IOException: Attempted read from closed stream.
工具类代码:
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
public class PowerjobUtil {
@Value("${powerjob.ip}")
private String ip;
private static HttpClient httpClient;
public String get(String uri) {
if (httpClient == null) {
httpClient = HttpClients.createDefault();
}
HttpGet getMethod = new HttpGet("http://" + ip + uri);
log.info("请求engine-server接口:{},入参:{}", uri);
getMethod.addHeader("accept", "*/*");
getMethod.addHeader("connection", "Keep-Alive");
try {
HttpResponse response = httpClient.execute(getMethod);
log.info("请求engine-server接口:{},出参:{}", uri);
HttpEntity httpEntity = response.getEntity();
return EntityUtils.toString(httpEntity);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String post(String uri, String requestJson) {
if (httpClient == null) {
httpClient = HttpClients.createDefault();
}
HttpPost postMethod = new HttpPost("http://" + ip + uri);
log.info("请求engine-server接口:{},入参:{}", uri, requestJson);
postMethod.addHeader("Content-Type", "application/json");
postMethod.addHeader("accept", "*/*");
postMethod.addHeader("connection", "Keep-Alive");
postMethod.setEntity(new StringEntity(requestJson, StandardCharsets.UTF_8));
try {
HttpResponse response = httpClient.execute(postMethod);
HttpEntity httpEntity = response.getEntity();
log.info("请求engine-server接口:{},出参:{}", uri, EntityUtils.toString(httpEntity));
return EntityUtils.toString(httpEntity);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2、逻辑调用
插入任务管理新后再往PowerJob表同步数据,同步是执行两个方法
#插入任务管理新后再往PowerJob表同步数据
ResultDTO result = auditTaskService.savePowerjobWorkflowInfo(req);
if (!result.isSuccess()) {
log.error("update Task Config sync powerjob_workflow_info fail."+result.getMessage());
return R.fail(result.getMessage());
}
ResultDTO resultInfo = auditTaskService.savePowerjobWorkflowNodeInfo(req);
if (!resultInfo.isSuccess()) {
log.error("update Task Config sync powerjob_workflow_node_info fail."+resultInfo.getMessage());
return R.fail(resultInfo.getMessage());
}
工具栏里最初返回数据是这样写的:
HttpResponse response = httpClient.execute(postMethod);
HttpEntity httpEntity = response.getEntity();
log.info("请求engine-server接口:{},出参:{}", uri, EntityUtils.toString(httpEntity));
return EntityUtils.toString(httpEntity);
3、分析原因
提供的 post 方法实现,看起来您在使用一个全局的 httpClient 对象来发送HTTP POST请求。这可能会导致流在某个请求完成后被关闭,从而影响到后续的请求。
上述工具类中的 post 方法,是每个请求都创建一个新的 HttpPost 对象。
不要在 post 方法中关闭 httpClient 对象,因为它被设计为可以在多个请求之间重用。然而,根据提示错误信息,问题可能出现在 EntityUtils.toString(httpEntity) 这一行。EntityUtils.toString(httpEntity) 会消耗掉 HttpEntity 中的内容,并且在使用完毕后关闭底层的流。如果您在后续的请求中尝试再次读取这个流,就会遇到 Attempted read from closed stream 的错误。
4、处理方法
要解决这个问题,您可以考虑以下几种方法:
确保 post 方法中的 httpEntity 只被读取一次。
如果需要在多个地方读取 httpEntity 的内容,可以考虑使用 BufferedHttpEntity 来包装原始的 HttpEntity,这样就可以多次读取内容而不会关闭底层的流。
使用 try-with-resources 或者确保在每次请求后都正确关闭 HttpPost 对象,但是不要关闭 httpClient。
最后改成这样就好了。
HttpResponse response = httpClient.execute(postMethod);
HttpEntity httpEntity = response.getEntity();
// 使用BufferedHttpEntity来包装原始的HttpEntity
HttpEntity bufferedHttpEntity = new BufferedHttpEntity(httpEntity);
String responseString = EntityUtils.toString(bufferedHttpEntity);
log.info("请求engine-server接口:{},出参:{}", uri, responseString);
return responseString;