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

时间:2011-06-01 10:05:31

标签: java jsf-2 cdi jboss-weld modular

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

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

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

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

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

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

关于我应该在哪里找出这个问题的任何建议? (如果我先找到答案,我会在这里发布答案)

3 个答案:

答案 0 :(得分:32)

我知道您的问题基本上归结为如何在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>

想象一下,/META-INF/resources/foo/bar.xhtml中有一个random.jar,那么你可以按常规方式包含它

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

甚至动态

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

注意:自Servlet 3.0和更新的JBoss / JSF 2.0版本以来,如果将文件保存在ResourceResolver文件夹中,则不需要整个/META-INF/resources方法。上述ResourceResolver仅在Servlet 2.5或更早的JBoss / JSF版本中是必需的,因为它们在META-INF资源解析中存在缺陷。

另见:

答案 1 :(得分:5)

我一直在寻找有关同一主题的信息,并且发现了这个链接:How-to: Modular Java EE Applications with CDI and PrettyFaces对我来说效果非常好。

答案 2 :(得分:2)

顺便说一下..当你使用缝焊(目前正在集成到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;
}

现在您可以使用菜单栏中的按钮更改面板中显示的视图,这样可以在不重新加载页面的情况下进行导航。

一些建议(或“我学到的东西”):

  1. 您可能想为getTopMenuItems方法选择一个大范围
  2. 不要嵌套ui:include标记。不幸的是,这是不可能的(例如,您的view1.xhtml不能包含其他组合)。我真的 我希望你可以真正建立这样的东西 这个模块化的jsf视图,有点像没有的portlet portlets .. = D
  3. 做ui:包含像tabviews这样的容器组件也证明是有问题的。
  4. 通常不建议将JSTL(c:forEach)和JSF混合使用。我仍然觉得这是以ui工作的唯一方式:重复获取 评估太晚,例如您的收录内容不会显示。