如何创建模块化 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 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章