26

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

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

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

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

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

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

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

4

3 回答 3

34

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

您可以通过ResourceResolver在 JAR 中放置自定义来完成此操作。

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.xhtmlin 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资源解析中存在错误。

也可以看看:

于 2011-06-01T12:21:35.717 回答
5

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

于 2012-03-25T17:49:16.137 回答
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 的 portlet .. =D
  3. 做 ui:include 在容器组件中,如 tabviews 也被证明是有问题的。
  4. 通常不建议混合使用 JSTL (c:forEach) 和 JSF。我仍然发现这是作为 ui:repeat 评估太晚的唯一方法,例如您包含的内容没有出现。
于 2013-06-06T22:52:21.020 回答