有没有办法从 JSF 支持 bean 操作方法提供文件下载?我已经尝试了很多东西。主要问题是我无法弄清楚如何获取OutputStream
响应以便将文件内容写入其中。我知道如何使用 a 来执行此操作Servlet
,但这不能从 JSF 表单中调用,并且需要一个新请求。
如何OutputStream
从当前获得响应FacesContext
?
有没有办法从 JSF 支持 bean 操作方法提供文件下载?我已经尝试了很多东西。主要问题是我无法弄清楚如何获取OutputStream
响应以便将文件内容写入其中。我知道如何使用 a 来执行此操作Servlet
,但这不能从 JSF 表单中调用,并且需要一个新请求。
如何OutputStream
从当前获得响应FacesContext
?
你可以通过一切ExternalContext
。在 JSF 1.x 中,您可以HttpServletResponse
通过ExternalContext#getResponse()
. 在 JSF 2.x 中,您可以使用一堆新的委托方法,ExternalContext#getResponseOutputStream()
而无需HttpServletResponse
从 JSF 引擎盖下获取。
在响应中,您应该设置Content-Type
标头,以便客户端知道哪个应用程序与提供的文件相关联。并且,你应该设置Content-Length
header,以便客户端可以计算下载进度,否则将是未知的。并且,如果您想要“另存为”对话框,则应将Content-Disposition
标题设置为,否则客户端将尝试内联显示它。最后只需将文件内容写入响应输出流。attachment
最重要的部分是调用FacesContext#responseComplete()
通知 JSF 在您将文件写入响应后它不应该执行导航和渲染,否则响应的结尾将被页面的 HTML 内容污染,或者在旧的 JSF 版本中,您将收到IllegalStateException
一条类似于getoutputstream() has already been called for this response
JSF 实现调用getWriter()
呈现 HTML 时的消息。
您只需要确保操作方法不是<h:commandLink>
由 ajax 请求调用,而是在您使用and触发时由正常请求调用<h:commandButton>
。Ajax 请求和远程命令由 JavaScript 处理,由于安全原因,JavaScript 没有工具来强制与 ajax 响应的内容进行“另存为”对话。
如果您使用例如 PrimeFaces <p:commandXxx>
,那么您需要确保通过ajax="false"
属性显式关闭 ajax。如果您使用的是 ICEfaces,则需要<f:ajax disabled="true" />
在命令组件中嵌套 a。
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = ec.getResponseOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = response.getOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
如果您需要从本地磁盘文件系统流式传输静态文件,请替换以下代码:
File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();
// ...
Files.copy(file.toPath(), output);
如果您需要流式传输动态生成的文件,例如 PDF 或 XLS,则只需在此处提供output
所使用的 API 需要OutputStream
.
例如 iText PDF:
String fileName = "dynamic.pdf";
String contentType = "application/pdf";
// ...
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();
例如 Apache POI HSSF:
String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
// ...
HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();
请注意,您不能在此处设置内容长度。因此,您需要删除该行来设置响应内容长度。这在技术上没有问题,唯一的缺点是最终用户会看到一个未知的下载进度。如果这很重要,那么您确实需要先写入本地(临时)文件,然后如前一章所示提供它。
如果您正在使用 JSF 实用程序库OmniFaces,那么您可以使用三种方便的Faces#sendFile()
方法之一,采用 aFile
或 anInputStream
或 a byte[]
,并指定文件应作为附件 ( true
) 还是内联 ( false
) 下载。
public void download() throws IOException {
Faces.sendFile(file, true);
}
是的,这段代码是完整的。你不需要responseComplete()
自己调用等等。此方法还可以正确处理特定于 IE 的标头和 UTF-8 文件名。你可以在这里找到源代码。
public void download() throws IOException
{
File file = new File("file.txt");
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletResponse response =
(HttpServletResponse) facesContext.getExternalContext().getResponse();
response.reset();
response.setHeader("Content-Type", "application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=file.txt");
OutputStream responseOutputStream = response.getOutputStream();
InputStream fileInputStream = new FileInputStream(file);
byte[] bytesBuffer = new byte[2048];
int bytesRead;
while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0)
{
responseOutputStream.write(bytesBuffer, 0, bytesRead);
}
responseOutputStream.flush();
fileInputStream.close();
responseOutputStream.close();
facesContext.responseComplete();
}
这对我有用:
public void downloadFile(String filename) throws IOException {
final FacesContext fc = FacesContext.getCurrentInstance();
final ExternalContext externalContext = fc.getExternalContext();
final File file = new File(filename);
externalContext.responseReset();
externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue());
externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName());
final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
FileInputStream input = new FileInputStream(file);
byte[] buffer = new byte[1024];
final ServletOutputStream out = response.getOutputStream();
while ((input.read(buffer)) != -1) {
out.write(buffer);
}
out.flush();
fc.responseComplete();
}
public static void download(
ByteArrayOutputStream baos,
String downloadFileName,
String contentType
) {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
externalContext.responseReset();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
response.reset();
response.setContentType(contentType);
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
Integer size = baos.size();
response.setHeader("Content-Length", size.toString());
response.setHeader(
"Content-Disposition",
"attachment; filename=\"" + downloadFileName + "\""
);
try {
try (OutputStream responseOs = response.getOutputStream()) {
baos.writeTo(responseOs);
}
}
catch (IOException e) {
throw new IOUncheckedException(e);
}
context.responseComplete();
}
这是完整的代码片段http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/
@ManagedBean(name = "formBean")
@SessionScoped
public class FormBean implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* Download file.
*/
public void downloadFile() throws IOException
{
File file = new File("C:\\docs\\instructions.txt");
InputStream fis = new FileInputStream(file);
byte[] buf = new byte[1024];
int offset = 0;
int numRead = 0;
while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0))
{
offset += numRead;
}
fis.close();
HttpServletResponse response =
(HttpServletResponse) FacesContext.getCurrentInstance()
.getExternalContext().getResponse();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=instructions.txt");
response.getOutputStream().write(buf);
response.getOutputStream().flush();
response.getOutputStream().close();
FacesContext.getCurrentInstance().responseComplete();
}
}
如果您希望在运行时生成文件,您可以更改文件读取逻辑。