如何创建模块化 JSF 2.0 应用程序?

Posted

技术标签:

【中文标题】如何创建模块化 JSF 2.0 应用程序?【英文标题】:How to create a modular JSF 2.0 application? 【发布时间】:2011-09-06 04:00:44 【问题描述】:

我有一个界面定义明确的应用程序。它使用 CDI 来解析模块,(特别是它使用 API 接口上的 Instance 注入点来解析模块)并通过接口将各种数据传回和第四次传递而不会出现问题。我有意将 API 和实现分开,并且模块仅从 API 继承以避免紧密耦合,并且应用程序仅通过运行时依赖关系知道模块,并且通过 API 完成数据传递。应用程序在没有模块的情况下运行良好,只需将 jar 放入 WEB-INF/lib 文件夹并重新启动应用服务器即可添加。

我遇到的问题是我希望模块创建视图的一部分,因此我想以可移植的方式调用 JSF 组件,或者按顺序从模块中包含让它呈现它的视图。我已经解决了我想要调用的模块,并且已经准备好对模块接口的引用。我最初想这样做的方式是做一个 ui:include 要求模块提供它的视图模板在哪里,但我不知道如何以有意义的方式回答该查询,因为视图解析是从应用程序完成的根目录,而不是库根目录。

执行摘要是,我不知道如何使用 JSF 为 .xhtml(模板/组件)文件跨越从应用程序到库的差距。

使用 CC 会很好,但我如何指定我在运行时需要一个特定的 CC 实例,而不是将那个硬编码到页面中?

我当然可以直接调用应用程序代码并要求它进行标记,但这似乎很暴力,一旦我有了标记,我不确定如何告诉 JSF 对其进行评估。也就是说,我可以想象一个组件会采用资源路径,获取标记并评估它,返回完整的标记,我只是不知道如何实现。

如果可能的话,我宁愿避免强迫模块开发人员采用繁重的 UIComponent 方法,这意味着要么采用动态方式执行 ui:include(或其他等效方式),要么采用动态方式调用 CC。 (我不介意在应用程序中编写一次 UIComponent 方法,如果这样可以让模块开发人员的生活更轻松)

关于我应该在哪里解决这个问题有什么建议吗? (如果我先找到答案,我会在这里发布)

【问题讨论】:

真是个好问题!我对自己感兴趣的一个。我希望有人有一个很好的答案来分享。 @maple_shaft 这是否回答了您的问题,还是我需要打开一个关于构建模块化 JSF 2.0 应用程序的社区 wiki?在这一点上,这对我来说非常简单,但这并不意味着每个阅读本文的人都可以这样做。 是的,这对我来说很有意义。在我当前的项目结束后的稍后时间,我计划重构通用模块以在未来的应用程序中重用,并且想知道如何做到这一点。我喜欢这个问题,所以我现在可以参考它。谢谢你的提问! 【参考方案1】:

我知道您的问题基本上可以归结为如何在 JAR 中包含 Facelets 视图?

您可以通过在 JAR 中放置自定义 ResourceResolver 来做到这一点。

public class FaceletsResourceResolver extends ResourceResolver 

    private ResourceResolver parent;
    private String basePath;

    public FaceletsResourceResolver(ResourceResolver parent) 
        this.parent = parent;
        this.basePath = "/META-INF/resources"; // TODO: Make configureable?
    

    @Override
    public URL resolveUrl(String path) 
        URL url = parent.resolveUrl(path); // Resolves from WAR.

        if (url == null) 
            url = getClass().getResource(basePath + path); // Resolves from JAR.
        

        return url;
    


在 webapp 的 web.xml 中配置如下:

<context-param>
    <param-name>javax.faces.FACELETS_RESOURCE_RESOLVER</param-name>
    <param-value>com.example.FaceletsResourceResolver</param-value>
</context-param>

假设你在random.jar 中有一个/META-INF/resources/foo/bar.xhtml,那么你可以像往常一样包含它

<ui:include src="/foo/bar.xhtml" />

甚至动态

<ui:include src="#bean.path" />

注意:由于 Servlet 3.0 和更新的 JBoss/JSF 2.0 版本,如果您将文件保存在 /META-INF/resources 文件夹中,则不需要整个 ResourceResolver 方法。上面的ResourceResolver 仅在 Servlet 2.5 或更早的 JBoss/JSF 版本中是强制性的,因为它们在 META-INF 资源解析中存在错误。

另见:

Packaging Facelets files (templates, includes, composites) in a JAR JSF facelets template packaging

【讨论】:

这正是我所追求的。知道必须有一个简单的方法来做到这一点,我只是不知道如何。非常感谢! 经过测试,这确实创造了我想要的解决方案。与此相比,我的最终解决方案可能会非常细微,但我将解析器放在 .war 文件中,并且将简单地指定视图进入模块 .jar 文件中的文件夹 X 作为接口契约的一部分。这样可以更好地分离模块和应用程序的编程问题,并允许我根据它们起源的模块解析特定资源(只有应用程序肯定知道这一点)以避免命名空间冲突问题。 “有趣的事实”... Websphere 8.0.0.8 仍然需要它,即使它支持 servlet 3.0! 很好的解决方案,但是如果多个模块找到一个资源会怎样?如果我希望模块不将它们的 xhtml 包含到 ui:include 而是附加它,我该如何完成? @Rapster:根据答案的最后一段,由于 Servlet 3.0 不需要整个 ResourceResolver 方法。无论如何,JSF 2.2 与 Servlet 2.5 不兼容。您可以删除ResourceResolver。您所要做的只是将资源放在 JAR 的/META-INF/resources 中。有关示例文件夹结构,另请参见答案底部的“打包 Facelets 文件”链接。如果您真的只是想从我的手中看到ResourceHandler 的具体示例,请参阅***.com/q/13292272【参考方案2】:

我正在寻找有关同一主题的信息并偶然发现此链接:How-to: Modular Java EE Applications with CDI and PrettyFaces 这对我来说非常有效。

【讨论】:

那将是一篇不错的文章。我相信它会增加一些来这里寻找这个主题的人的知识。感谢您发布此链接!【参考方案3】:

顺便说一句.. 当您使用缝焊(目前已集成到 apache deltaspike)时,您可以避免实现自己的资源解析器,这是一个非常有用的库,可补充 CDI(您的典型 Java EE 6 组件模型)

我也在 jsf 应用程序中尝试了模块化。基本上,我构建了一个带有工具栏的模板界面,其中填充了每个模块提供的按钮。通常,您将通过提供一个字符串列表作为命名对象来做到这一点:

@Produces
@SomethingScoped
@Named("topMenuItems")
public List<String> getTopMenuItems()
return Arrays.asList("/button1.xhtml", "/button2.xhtml", "/button3.xhtml");

注意每个按钮可能来自 jsf 应用程序的不同模块。 模板界面包含一个面板,其中

您可以通过以下方式在您的标记中使用它(风险自负;)):

....
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
....
<xy:toolbar>
  <xy:toolbarGroup>
    <c:forEach items="#topMenuItems" var="link">
      <ui:include src="#link" />
    </c:forEach>
  </xy:toolbarGroup>
</xy:toolbar>
<xy:panel>
  <ui:include src="#contentPath"/>
</xy:panel>

这是工具栏和内容面板。

一个简单的按钮或视图定义可能如下所示:

<ui:composition ...>
    <xy:commandButton actionListener="#topMenuController.switchContent()"
        value="Test" id="testbutton" />
</ui:composition>

让我们将此工件命名为 view1.xhtml

当这个按钮被按下时(它不会使用 actionListener 触发回发,我们想使用 ajax 重新加载内容)你的控制器中的 switchContentMethod 可能会改变 getContentPath 返回的字符串:

public void switchContent()
    contentPath = "/view1.xhtml";


@Produces
@SomethingScoped
@Named("contentPath")
public String getContentPath()
    return contentPath;

现在您可以使用菜单栏中的按钮更改面板中显示的视图,该按钮为您提供导航而无需重新加载页面。

一些建议(或“我学到了什么”):

    您可能希望为 getTopMenuItems 方法选择较大的范围 不要嵌套 ui:include 标记。不幸的是,这是不可能的(例如,您的 view1.xhtml 不能包含其他组合)。我真的 希望这样的事情成为可能,因为你真的可以建造 带有这个的模块化 jsf 视图,有点像 portlet,只是没有 门户网站.. =D 在容器组件(如 tabviews)中执行 ui:include 也被证明是有问题的。 通常不建议混合使用 JSTL (c:forEach) 和 JSF。我仍然发现这是作为 ui:repeat 工作的唯一方法 评估为时已晚您包含的内容没有出现。

【讨论】:

以上是关于如何创建模块化 JSF 2.0 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JSF 2.0 中使会话无效?

如何在 JSF 2.0 中重定向

JSF 2.0 在整个会话中从浏览器和以编程方式设置区域设置 [重复]

如何在 JSF 2.0 中使用 jQuery

JSF 2.0 中的 SessionScope

如何在 JSF 2.0 中创建自定义 404 消息?