项目开发中用到一些文件下载的功能.
使用org.apache.struts2.dispatcher.StreamResult时确有发现,在下载过程中终止下载,会引起服务端java.lang.IllegalStateException异常抛出,且无法关闭文件句柄.
在网上寻求解决办法,发现Jimmy Song在GitHub上的一篇文章https://github.com/41zone/StreamResultX,可解燃眉之急,在此感谢原创作者.
由于项目的需要,除了要解决struts2下载文件出现的问题之外,下载的文件大多数为临时文件,于是在Jimmy Song所贡献的代码之上,又做了一些改动,以支持下载完成之后(或下载过程中终止下载)自动删除临时文件.
源代码
/**
* <p>在此感谢Jimmy Song
* <br/>该类扩展了struts2中的result-type为stream的实体类,并重写了doExecute方法。
* <br/>该类修正了源于ResultStream的一些错误处理,在文档下载取消时释放HttpResponse对象的引用。
* <br/>防止java.lang.IllegalStateException异常抛出。
* <br/>使用原有ResultStream进行文件下载时,如若点击取消,Socket并不会断开,流也没有关闭。
* <br/>在JSP容器通过Response获取输出流之前,前面的流并没有关闭,所以会造成该异常的报出。
*
* @author Jimmy Song
* @version 1.2
* @see https://github.com/41zone/StreamResultX
*/
/**
* <p>借用Jimmy Song的StreamResultX,实现下载完之后删除文件
*
* @author ManerFan <a href="mailto:juniorfan@yeah.net"></a>
*/
public class StreamResultX extends StreamResult {
private static final long serialVersionUID = -8275283556955657976L;
/**
* 文件路径
*/
private String filePath;
/**
* 扩展重写StreamResult方法
*/
protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
resolveParamsFromStack(invocation.getStack(), invocation);
OutputStream oOutput = null;
/** 下载完成后需要删除的文件 */
File delFile = null;
try {
String path = (String) invocation.getStack().findValue(
conditionalParse(filePath, invocation));
if (null != path && !path.trim().isEmpty()) {
delFile = new File(path);
}
if (inputStream == null) {
// Find the inputstream from the invocation variable stack
inputStream = (InputStream) invocation.getStack().findValue(
conditionalParse(inputName, invocation));
}
if (inputStream == null) {
String msg = ("StreamResultX : Can not find a java.io.InputStream with the name ["
+ inputName + "] in the invocation stack. " + "Check the <param name=\"inputName\"> tag specified for this action.");
LOG.error(msg);
throw new IllegalArgumentException(msg);
}
// Find the Response in context
HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext()
.get(HTTP_RESPONSE);
// Set the content type
if (contentCharSet != null && !contentCharSet.equals("")) {
oResponse.setContentType(conditionalParse(contentType, invocation) + ";charset="
+ contentCharSet);
} else {
oResponse.setContentType(conditionalParse(contentType, invocation));
}
// Set the content length
if (contentLength != null) {
String _contentLength = conditionalParse(contentLength, invocation);
int _contentLengthAsInt = -1;
try {
_contentLengthAsInt = Integer.parseInt(_contentLength);
if (_contentLengthAsInt >= 0) {
oResponse.setContentLength(_contentLengthAsInt);
}
} catch (NumberFormatException e) {
LOG.warn("StreamResultX warn : failed to recongnize " + _contentLength
+ " as a number, contentLength header will not be set", e);
}
}
// Set the content-disposition
if (contentDisposition != null) {
oResponse.addHeader("Content-Disposition",
conditionalParse(contentDisposition, invocation));
}
// Set the cache control headers if neccessary
if (!allowCaching) {
oResponse.addHeader("Pragma", "no-cache");
oResponse.addHeader("Cache-Control", "no-cache");
}
// Get the outputstream
oOutput = oResponse.getOutputStream();
if (LOG.isDebugEnabled()) {
LOG.debug("StreamResultX : Streaming result [" + inputName + "] type=["
+ contentType + "] length=[" + contentLength + "] content-disposition=["
+ contentDisposition + "] charset=[" + contentCharSet + "]");
}
// Copy input to output
byte[] oBuff = new byte[bufferSize];
int iSize;
try {
LOG.debug("StreamResultX : Streaming to output buffer +++ START +++");
while (-1 != (iSize = inputStream.read(oBuff))) {
oOutput.write(oBuff, 0, iSize);
}
LOG.debug("StreamResultX : Streaming to output buffer +++ END +++");
// Flush
oOutput.flush();
} catch (Exception e) {
LOG.warn("StreamResultX Warn : socket write error");
if (oOutput != null) {
try {
oOutput.close();
} catch (Exception e1) {
oOutput = null;
}
}
} finally {
if (inputStream != null)
inputStream.close();
if (oOutput != null)
oOutput.close();
if (null != delFile // 可以删除
&& delFile.exists() // 文件存在
&& delFile.isFile()) { // 是常规文件
boolean delSucc = delFile.delete();
LOG.debug("delete file [" + filePath + " ], status [" + delSucc + "]");
}
}
} finally {
if (inputStream != null)
inputStream.close();
if (oOutput != null)
oOutput.close();
if (null != delFile // 可以删除
&& delFile.exists() // 文件存在
&& delFile.isFile()) { // 是常规文件
boolean delSucc = delFile.delete();
LOG.debug("delete file [" + filePath + " ], status [" + delSucc + "]");
}
}
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
}
使用方法
首先,在struts.xml对应的package中加入
<result-types>
<result-type name="streamx" class="manerfan.struts2.dispatcher.StreamResultX"/>
</result-types>
其次,在对应下载的action中,将原有的type="stream"类型修改为type="streamx"即可
<result name="success" type="streamx">
<param name="contentType">application/octet-stream;charset=ISO8859-1</param>
<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
<param name="bufferSize">4096</param>
<param name="inputName">inputStream</param>
<param name="filePath">realFilePath</param>
</result>
配置中,[
contentDisposition
]downloadFileName为浏览器下载框默认下载文件名;[
inputName
]inputStream为指向下载文件的InputStream;[
filePath
]realFilePath为所下载文件的绝对路径.
以上三个参数需要在对应action中设置getter方法
若配置filePath参数,则在下载结束后自动删除下载的文件
若不配置filePath参数,则在下载结束后不删除下载的文件