1

我的主要问题是:是否有使用 JSF 2 和 CDI 并使用可收藏的 URL 来提供二进制文件(PDF、文档等)的“良好做法”?

我已经阅读了JSF 2 规范(JSR 314)并且我看到它存在一个“资源处理”段落。但它似乎仅用于提供放入 war 或 jar 文件中的静态文件。我真的不明白它是否存在通过注册一些特定的 ResourceHandler 来进行交互的方式......

实际上,我已经习惯了 Seam 的 2 种方法:使用方法扩展AbstractResource类并在URL 前缀之后声明要服务的路径并在文件中声明。getResource(HttpServletRequest, HttpServletResponse)getResourcePath()<webapp>/seam/resource/SeamResourceServletweb.xml

这就是我所做的。

我首先看到了如何使用 JSF 2.0 下载存储在数据库中的文件并尝试实现它。

<f:view ...

    <f:metadata>
        <f:viewParam name="key" value="#{containerAction.key}"/>
        <f:event listener="#{containerAction.preRenderView}" type="preRenderComponent" />
    </f:metadata>

    ...

    <rich:dataGrid columns="1" value="#{containerAction.container.files}" var="file">
        <rich:panel>
                <h:panelGrid columns="2">
                    <h:outputText value="File Name:" />
                    <h:outputText value="#{file.name}" />
                </h:panelGrid>
                <h:form>
                    <h:commandButton value="Download" action="#{containerAction.download(file.key)}" />
                </h:form>
        </rich:panel>
    </rich:dataGrid>

这是豆子:

@Named
@SessionScoped
public class ContainerAction {

    private Container container;

    /// Injections
    @Inject @DefaultServiceInstance
    private Instance<ContainerService> containerService;

    /// Control methods
    public void preRenderView(final ComponentSystemEvent event) {
        container = containerService.get().loadFromKey(key);
    }

    /// Action methods
    public void download(final String key) throws IOException {
        final FacesContext facesContext = FacesContext.getCurrentInstance();

        HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();

        final ContainerFile containerFile = containerService.get().loadFromKey(key);
        final InputStream containerFileStream = containerService.get().read(containerFile);

        response.setHeader("Content-Disposition", "attachment;filename=\""+containerFile.getName()+"\"");
        response.setContentType(containerFile.getContentType());
        response.setContentLength((int) containerFile.getSize());

        IOUtils.copy(containerFileStream, response.getOutputStream());

        response.flushBuffer();

        facesContext.responseComplete();
    }

    /// Getters / setters
    public Container getContainer() {
        return container;
    }
}

在这里,我必须切换到 Tomcat 7(我使用的是 6)才能正确解释该 EL 表达式。它@SessionScoped起作用了,但不起作用@RequestScoped(当我单击按钮时,什么也没发生)。

但后来我想使用链接而不是按钮。

我试过<h:commandLink value="Download" action="#{containerAction.download(file.key)}" />了,但它会生成一些丑陋的 javascript 链接(不可收藏)。

阅读 JSF 2 规范,似乎有一个“Bookmarkability”功能,但并不清楚如何使用它。

实际上,它似乎只适用于视图,所以我尝试创建一个空视图并创建了一个h:link

<h:link outcome="download.xhtml" value="Download">
    <f:param name="key" value="#{file.key}"/>
</h:link>
<f:view ...>
    <f:metadata>
        <f:viewParam name="key" value="#{containerFileDownloadAction.key}"/>
        <f:event listener="#{containerFileDownloadAction.download}" type="preRenderComponent" />
    </f:metadata>
</f:view>
@Named
@RequestScoped
public class ContainerFileDownloadAction {

    private String key;

    @Inject @DefaultServiceInstance
    private Instance<ContainerService> containerService;

    public void download() throws IOException {
        final FacesContext facesContext = FacesContext.getCurrentInstance();

        // same code as previously
        ...

        facesContext.responseComplete();
    }


    /// getter / setter for key
    ...
}

但后来,我有一个java.lang.IllegalStateException: "getWriter()" has already been called for this response.

逻辑就像视图启动时一样,它使用 getWritter 来初始化响应。

所以我创建了一个 Servlet 来完成这项工作并创建了以下内容h:outputLink

<h:outputLink value="#{facesContext.externalContext.request.contextPath}/download/">
    <h:outputText value="Download"/>
    <f:param name="key" value="#{file.key}"/>
</h:outputLink>

但即使最后一种技术为我的文件提供了一个可收藏的 URL,它也不是真正的“JFS 2”......

你有什么建议吗?

4

3 回答 3

2

我同意 BalusC。通常,应用程序不是纯粹的 JSF 应用程序,而是 Java EE 应用程序。

在 Java EE 中处理 http 请求时,存在除了 JSF 视图之外的其他东西并不是没有意义的。在 Java EE 6 中,您命名的 CDI bean 也可以直接映射到使用 JAX-RS 的路径。这是使用 Servlet 的替代方法。在这种情况下,您将使用@Produces 和@Path(参见例如使用JERSEY 的输入和输出二进制流?)。

另一方面,<f:viewParam>JSF 的一个优点是您可以轻松地将验证器附加到它。目前,Servlet 和 JAX-RS 资源均不支持该功能。

<h:link>使用起来也比<h:outputLink value="#{facesContext.externalContext.request.contextPath}/...">一直写更舒服。但是,可以通过将此部分包装在 Facelets 标记或复合组件中来缓解这种情况。

(我认为如果未来版本的规范在 JSF 中提供一个链接标记以直接链接到 JAX-RS 资源(通过可选的容器启动验证来确保链接是合法的),那就太好了)。

于 2011-06-12T21:10:05.817 回答
1

JSF 从一开始就被设计为一个 MVC 框架,而不是一些 REST 文件服务。

servlet 非常适合这项工作。注释它@WebServlet以获得更好的 Java EE 6 感觉。

于 2011-06-08T20:38:50.220 回答
0

事实上,使用PrettyFaces URLRewriteFilter -> http://ocpsoft.org/prettyfaces/serving-dynamic-file-content-with-prettyfaces/可以直接解决这个问题

这个博客解释了如何做你想做的事,而不必使用一个全新的 MVC 框架。

于 2012-03-30T21:36:53.537 回答