我有以下模板(masterLayout.xhtml):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view contentType="text/html">
<ui:insert name="metadata"/>
<h:head>
<title><ui:insert name="windowTitle"/> | MySite</title>
</h:head>
<h:body>
<div id="container">
<div id="header">
<ui:insert name="header">
<ui:include src="/WEB-INF/templates/header.xhtml"/>
</ui:insert>
</div>
<div id="content">
<ui:insert name="content"/>
</div>
<div id="footer">
<ui:insert name="footer">
<ui:include src="/WEB-INF/templates/footer.xhtml"/>
</ui:insert>
</div>
</div>
</h:body>
</f:view>
</html>
和使用它的页面(search.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/WEB-INF/templates/masterLayout.xhtml">
<ui:define name="metadata">
<f:metadata>
<f:viewParam name="address" value="#{searchBean.address}"/>
<f:event type="preRenderView" listener="#{userSessionBean.preRenderViewCookieLogin(e)}"/>
<f:event type="preRenderView" listener="#{searchBean.preRenderView(e)}"/>
</f:metadata>
</ui:define>
<ui:define name="windowTitle">#{searchBean.address}</ui:define>
<ui:define name="content">
<!-- Content goes here -->
</ui:define>
</ui:composition>
</h:body>
</html>
问题是我想在模板中调用userSessionBean.preRenderViewCookieLogin(e)
,因为还有很多其他页面。此方法检查用户是否已登录(根据会话状态),如果没有,则检查cookie是否可用,可用于记录用户,如果是,(如果有效),则自动记录用户。系统在上面的代码中工作,但是当我尝试将其推入模板时,我的视图参数不再被设置。
以上是上述修改后的版本,其中userSessionBean.preRenderViewCookieLogin(e)
被推送到模板。
masterLayout.xhtml:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view contentType="text/html">
<f:metadata>
<f:event type="preRenderView" listener="#{userSessionBean.preRenderViewCookieLogin(e)}"/>
<ui:insert name="metadata"/>
</f:metadata>
<h:head>
<title><ui:insert name="windowTitle"/> | MySite</title>
</h:head>
<h:body>
<div id="container">
<div id="header">
<ui:insert name="header">
<ui:include src="/WEB-INF/templates/header.xhtml"/>
</ui:insert>
</div>
<div id="content">
<ui:insert name="content"/>
</div>
<div id="footer">
<ui:insert name="footer">
<ui:include src="/WEB-INF/templates/footer.xhtml"/>
</ui:insert>
</div>
</div>
</h:body>
</f:view>
</html>
search.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/WEB-INF/templates/masterLayout.xhtml">
<ui:define name="metadata">
<f:viewParam name="address" value="#{searchBean.address}"/>
<f:event type="preRenderView" listener="#{searchBean.preRenderView(e)}"/>
</ui:define>
<ui:define name="windowTitle">#{searchBean.address}</ui:define>
<ui:define name="content">
<!-- Content goes here -->
</ui:define>
</ui:composition>
</h:body>
</html>
请注意,我已将<f:metadata/>
标记移至模板。仅此一点就是问题,因为删除userSessionBean.preRenderViewCookieLogin(e)
没有任何区别。我还尝试了有效代码的变体,即将userSessionBean.preRenderViewCookieLogin(e)
移动到模板中,这意味着它不能位于<f:metadata/>
标记内。在这种情况下,在设置了所有视图参数并调用searchBean.preRenderView(e)
之后执行该方法。我希望在调用任何页面userSessionBean.preRenderViewCookieLogin(e)
之前调用preRenderView(e)
,而不是之后调用<f:metadata/>
。为了好玩,我尝试在userSessionBean.preRenderViewCookieLogin(e)
周围调用<f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase(e)}">
,调用此方法,但没有设置视图参数。
所以,我想知道:
修改
我只是尝试了别的东西 - 一个阶段事件:
e
这是在masterLayout.xhtml中。根本没有被召唤;不适用于任何阶段。
修改
删除了<f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase}">
(该死的NetBeans!):
preRenderView
这仅在渲染响应阶段之前调用,这当然意味着在{{1}}事件被引发之后调用它。
答案 0 :(得分:13)
为什么会发生这种情况并且有办法解决它?
来自<f:metadata>
tag documentation(第2段的重点是我的):
声明此视图的元数据构面。这必须是
<f:view>
的孩子。 此标记必须位于给定viewId的顶级XHTML文件中,或位于模板客户端中,但不能位于模板中。实现必须确保构面的直接子项为{{1即使只有一个方面的子项。实现必须将UIPanel
的id设置为UIPanel
符号常量的值。
所以,它必须放在顶视图中,而不是模板中。
有没有更好的方法来确保在每个页面之前为每个页面调用相同的方法?
在您的特定情况下,将登录用户存储为会话范围的托管bean而不是cookie的属性,并在相应的URL模式上使用filter进行检查。会话范围的托管bean在过滤器中可用作UIViewRoot.METADATA_FACET_NAME
属性。本土饼干是不必要的,因为你基本上在这里重新发明了HttpSession
。除非你想要一个“记住我”的设施,但这不应该以这种方式解决。也可以在过滤器中进行。
答案 1 :(得分:0)
我使用PhaseListener
改为工作。
public class CookieLoginPhaseListener implements PhaseListener
{
@Override
public void beforePhase(PhaseEvent event)
{
}
@Override
public void afterPhase(PhaseEvent event)
{
UserSessionBean userSessionBean = Util.lookupCdiBean("userSessionBean");
userSessionBean.handleAuthCookie();
}
@Override
public PhaseId getPhaseId()
{
return PhaseId.RESTORE_VIEW;
}
}
这里有一点点查找魔法:
public static <T> T lookupCdiBean(String name)
{
return (T) FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{" + name + "}", Object.class);
}
感谢@BalusC:JSF - get managed bean by name。由于我使用的是CDI,因此我无法使用更具声明性的@ManagedProperty
选项。不过没什么大不了的。