PrimeFaces中StreamedContent与p:media无法更新PDF内容求助
解决PrimeFaces中动态PDF重复显示首次文件的问题
嘿,我完全懂你遇到的这个糟心问题——这其实是PrimeFaces里StreamedContent结合无状态Bean时常见的状态管理+浏览器缓存双重坑,我来给你一步步拆解解决办法:
问题根源分析
- 无状态Bean的状态丢失:你用了
@StatelessEJB,这类Bean是池化复用的,每次请求可能拿到不同的实例,你在verDocumento里设置的streamedContent和doc变量,下一次请求时可能已经被其他请求覆盖或者丢失了。 - 浏览器缓存:
p:media加载PDF时,浏览器会默认缓存资源URL,第一次加载后,后续请求会直接用缓存,不会再去服务器拿新的流。 StreamedContent的加载时机:p:media的value是在对话框显示后才发起单独的资源请求,这时候你之前设置的实例变量已经不在当前Bean实例里了。
具体解决方案
1. 把Bean改成视图作用域(ViewScoped)
无状态Bean不适合这种需要在同一页面多次交互保留状态的场景,换成JSF的@ViewScoped,让Bean和当前视图绑定,状态会一直保留到你离开这个页面:
@Named(value = "visualizarDocumento") @ViewScoped // 替换原来的@Stateless public class VisualizarDocumentoController implements Serializable { // 必须实现Serializable private InformacionDocumento currentDoc; // 保存当前选中的文档,替代原来的doc变量 // 不需要再把streamedContent作为实例变量保存 public void verDocumento(InformacionDocumento documento) { this.currentDoc = documento; // 只需要保存当前选中的文档 RequestContext requestContext = RequestContext.getCurrentInstance(); requestContext.update(":visualizacion"); requestContext.execute("PF('visualizarPDF').show()"); } public StreamedContent getStreamedContent() { // 如果没有选中文档,返回空的默认流 if (currentDoc == null) { return DefaultStreamedContent.builder() .contentType("application/pdf") .build(); } // 每次请求都重新解码生成流 byte[] decode = Base64.getDecoder().decode(currentDoc.getArchivo().getBase64()); ByteArrayInputStream bis = new ByteArrayInputStream(decode); return DefaultStreamedContent.builder() .name(currentDoc.getNombreArchivo()) // 可选:设置文件名,方便用户下载时识别 .contentType("application/pdf") .stream(() -> bis) .build(); } // currentDoc需要getter,因为在XHTML里要用到 public InformacionDocumento getCurrentDoc() { return currentDoc; } public void setCurrentDoc(InformacionDocumento currentDoc) { this.currentDoc = currentDoc; } }
2. 修改XHTML的p:media,添加防缓存参数
给p:media加上动态参数,让浏览器认为每次都是新的请求,彻底绕过缓存:
<p:dialog widgetVar="visualizarPDF" modal="true"> <h:form id="visualizacion" > <p:media value="#{visualizarDocumento.streamedContent}" width="750" height="560" player="pdf"> <!-- 用当前文档的ID和时间戳作为参数,确保每次请求URL不同 --> <f:param name="docId" value="#{visualizarDocumento.currentDoc.id}" /> <f:param name="t" value="#{now.time}" /> </p:media> </h:form> </p:dialog>
3. 优化XHTML的表单结构(可选但推荐)
把h:form从p:column里移出来,放在p:dataTable外面,避免每个按钮都单独生成表单(PrimeFaces允许在dataTable外面用一个表单包裹所有按钮):
<h:form> <p:dataTable var="documento" value="#{docController.obtenerDocumentos()}" filteredValue="#{docController.filteredValue}" > <!-- 其他列 --> <p:column> <f:facet name="header"> <h:outputText value="Acciones"/> </f:facet> <center> <p:commandButton styleClass="ui-priority-primary" icon="fa fas fa-eye" update=":visualizacion" action="#{visualizarDocumento.verDocumento(documento)}" value="Ver documento" onstart="PF('cargando').show()" onsuccess="PF('cargando').hide()"/> <br/> <p:commandButton styleClass="ui-priority-primary" icon="fa fas fa-align-justify" action="#{docController.respuestaDocumento(documento)}" value="Ver respuestas"/> </center> </p:column> </p:dataTable> </h:form>
为什么这样能解决问题?
@ViewScoped:保证了Bean的状态和当前页面绑定,每次点击不同文档时,currentDoc都会被正确更新,不会因为无状态Bean的池化导致状态混乱。- 动态生成StreamedContent:每次
p:media请求资源时,都会基于当前的currentDoc重新解码生成流,确保拿到的是最新选中的文档。 - 防缓存参数:
docId和时间戳让浏览器每次请求的URL都不一样,彻底避免了缓存旧PDF的问题。
内容的提问来源于stack exchange,提问作者Julian Solarte
相关产品推荐
相关产品推荐

