我有一个定义良好的应用程序的应用程序。它使用CDI来解析模块,(特别是它使用API接口上的实例<>注入点来解析模块)并通过接口传回各种数据而没有问题。我故意将API和实现分开,并且模块仅从API继承以避免紧密耦合,并且应用程序仅通过运行时依赖性知道模块,并且通过API完成数据传递。应用程序在没有模块的情况下运行正常,只需将jar放入WEB-INF / lib文件夹并重新启动应用服务器即可添加。
我遇到的问题是我希望模块创建视图的一部分,因此我想以可移植的方式调用JSF组件,或者按顺序从模块中执行包含让它呈现它的视图。我已经解决了我想要调用的模块,并准备好了对模块接口的引用。我最初认为这样做的方法是做一个ui:include要求模块提供它的视图模板的位置,但我不知道如何以有意义的方式回答该查询,因为视图解析是从应用程序完成的root,而不是库根。
执行摘要是我不知道如何使用JSF为.xhtml(模板/组件)文件从应用程序跳到库。
使用CC会很好,但是如何在运行时指定我想要一个特定的CC实例,而不是将其硬编码到页面中?
我当然可以直接调用应用程序代码并要求它进行标记,但这似乎非常暴力,一旦我有了标记,我不确定如何告诉JSF评估它。也就是说,我可以想象一个组件可以获取资源路径,获取标记并对其进行评估,返回已完成的标记,我只是不知道如何实现它。
我宁愿避免强制模块开发人员尽可能采用重型UIComponent方法,这意味着要么采用动态方式执行ui:include(或某些等效方法),要么采用动态方式调用CC。 (我不介意在应用程序中编写UIComponent方法ONCE,如果这样可以使模块开发人员的生活更轻松)
关于我应该在哪里找出这个问题的任何建议? (如果我先找到答案,我会在这里发布答案)
答案 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;
}
现在您可以使用菜单栏中的按钮更改面板中显示的视图,这样可以在不重新加载页面的情况下进行导航。
一些建议(或“我学到的东西”):