JSF 2 facelets模板和页面中的<f:metadata/>是什么?

9

我有以下模板(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)之后执行。我希望在调用任何页面的preRenderView(e)之前先调用userSessionBean.preRenderViewCookieLogin(e)。只是为了好玩,我尝试在userSessionBean.preRenderViewCookieLogin(e)周围加上<f:metadata/>标签,它调用了这个方法,但没有设置视图参数。
因此,我想知道:
1.为什么会发生这种情况,有没有办法解决?
2.有没有更好的方法来确保相同的方法在任何其他操作之前被调用?
编辑: 我刚才尝试了另外一件事 - 一个阶段事件: <f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase(e)}"> 这是在masterLayout.xhtml中。它根本没有被调用;不为任何阶段。 编辑: 删除了e(该死的NetBeans!): <f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase}"> 这仅在渲染响应阶段之前被调用,这当然意味着它在引发preRenderView事件之后被调用。
2个回答

14
"为什么会出现这种情况,有没有办法解决?"
<f:metadata> 标签文档(第二段的重点是我的):
"声明此视图的元数据方面。这必须是 <f:view> 的子级。 此标签必须驻留在给定 viewId 的顶级 XHTML 文件中,或者在模板客户端中,但不能在模板中。 实现必须确保 facet 的直接子项是 UIPanel,即使 facet 只有一个子项。实现必须将 UIPanel 的 ID 设置为 UIViewRoot.METADATA_FACET_NAME 符号常量的值。"
所以,它真的必须放在顶层视图中,而不是模板中。"
在您的特定情况下,将已登录用户存储为会话范围受管bean的属性,而不是cookie,并在适当的URL模式上使用filter来检查它。会话范围的托管bean在过滤器中作为HttpSession属性可用。自制的cookie是不必要的,因为您基本上正在重新发明这里的HttpSession。除非您想要一个“记住我”的功能,但这不应该以这种方式解决。也可以在过滤器中执行此操作。
另请参见:

感谢@BalusC。我实际上正在尝试实现“记住我”的功能。我想要调用的方法每次都会首先检查用户是否已登录,根据会话范围管理的bean的状态。这是我已经有一段时间在工作的东西。我刚开始构建“记住我”功能,以便用户在会话过期后不必再次登录。难道没有一种通用的方法可以在每个页面的preRenderView之前调用相同的方法吗? - Steve
1
移除那个 (e)。在作用域中没有这样的东西。 - BalusC
我最终使用了一个阶段监听器。该阶段监听器查找userSessionBean(会话作用域),然后调用该bean上执行cookie登录的方法。我在这里使用并点赞了您的一些代码here,以便在阶段监听器中按名称获取bean。我很快就会在这里发布解决方案。 - Steve
我没有选择过滤器选项,因为我认为那太过于复杂了。由于我在JSF中管理所有身份验证,所以在JSF之外处理其中一部分就没有意义,而一个简单的阶段监听器就可以解决问题。我真的很感激你的回答。啊,见鬼,我会把这个给你,因为你实际上回答了第一部分并指引我正确地解决了第二部分。 - Steve

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;
    }
}

而这里就是一点点的Bean查找魔法:

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选项。不过这并不是什么大问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接