PrimeFaces StreamedContent从Tomcat独立上下文下载的路径问题
PrimeFaces FileDownload 访问Tomcat独立上下文文件的问题
问题背景
我正在使用PrimeFaces FileDownload组件,应用中用户上传的文件存储在Tomcat server.xml 配置的独立media上下文:
<Context docBase="/path/to/media" path="/media" />
页面端的下载代码如下:
<script> //<![CDATA[ function start() { PF('statusDialog').show(); } function stop() { PF('statusDialog').hide(); } //]]> </script> <h:form> <p:commandButton value="Download" ajax="false" onclick="PrimeFaces.monitorDownload(start, stop);" icon="pi pi-arrow-down" styleClass="mr-2"> <p:fileDownload value="#{fileDownloadView.file}"/> </p:commandButton> <p:commandButton value="Ajax Download" icon="pi pi-arrow-down" styleClass="ui-button-outlined"> <p:fileDownload value="#{fileDownloadView.file}"/> </p:commandButton> </h:form>
参考PrimeFaces示例的后台Bean,我尝试用/media/path/to/file和/path/to/media/path/to/file两种路径调用getResourceAsStream,但都抛出空指针异常:
java.lang.NullPointerException: Cannot invoke "java.io.InputStream.read(byte[])" because "input" is null
目前我通过直接使用文件系统绝对路径创建InputStream实现了下载,但希望找到能用/media/开头路径实现的方法。
可行解决方案
方法1:跨上下文访问ServletContext
Tomcat中如果主应用配置了crossContext="true",可以直接获取/media上下文的ServletContext来读取资源:
- 先修改主应用的
server.xml配置,添加crossContext="true":
<Context docBase="/path/to/main/app" path="/main-app" crossContext="true" />
- 后台Bean代码调整:
@Named @RequestScoped public class FileDownloadView { private StreamedContent file; public FileDownloadView() { try { // 获取/media上下文的ServletContext实例 ServletContext mediaContext = (ServletContext) FacesContext.getCurrentInstance() .getExternalContext().getContext("/media"); if (mediaContext != null) { // 用/media上下文内的相对路径读取文件 InputStream stream = mediaContext.getResourceAsStream("/path/to/file"); file = DefaultStreamedContent.builder() .name("sample_pdf.pdf") .contentType("application/pdf") .stream(() -> stream) .build(); } } catch (Exception e) { e.printStackTrace(); } } public StreamedContent getFile() { return file; } }
方法2:映射真实路径读取文件
如果不想配置跨上下文,可以通过获取/media的真实路径(或直接使用配置的docBase)来拼接文件路径:
@Named @RequestScoped public class FileDownloadView { private StreamedContent file; public FileDownloadView() { try { // 尝试获取/media上下文的真实路径,若返回null则直接用配置的docBase String mediaRoot = FacesContext.getCurrentInstance() .getExternalContext().getRealPath("/media"); if (mediaRoot == null) { mediaRoot = "/path/to/media"; // 建议从配置文件读取,避免硬编码 } File targetFile = new File(mediaRoot + "/path/to/file"); InputStream stream = new FileInputStream(targetFile); file = DefaultStreamedContent.builder() .name("sample_pdf.pdf") .contentType("application/pdf") .stream(() -> stream) .build(); } catch (Exception e) { e.printStackTrace(); } } public StreamedContent getFile() { return file; } }
注意:getRealPath在应用打包为WAR时可能返回null,因此最好将/path/to/media配置到环境变量或配置文件中,避免硬编码。
方法3:主应用内配置资源映射
还可以在主应用的web.xml中添加上下文参数,将/media路径映射到实际文件系统目录:
<context-param> <param-name>javax.faces.WEBAPP_RESOURCES_DIRECTORY</param-name> <param-value>/path/to/media</param-value> </context-param>
之后就可以用getResourceAsStream("/path/to/file")直接读取,但这种方法会覆盖默认的JSF资源目录,若主应用本身有资源需要访问,需谨慎使用。
内容的提问来源于stack exchange,提问作者Ted Spradley
相关产品推荐
相关产品推荐

