【问题标题】:JSF 2 facelets <f:metadata/> in template and page模板和页面中的 JSF 2 facelets <f:metadata/>
【发布时间】:2011-09-27 18:42:11
【问题描述】:

我有以下模板(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>

搜索.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>

请注意,我已将 &lt;f:metadata/&gt; 标记移至模板。仅此一项就是问题,因为删除 userSessionBean.preRenderViewCookieLogin(e) 没有任何区别。我还尝试了对有效代码的变体,即将userSessionBean.preRenderViewCookieLogin(e) 移动到模板中,这意味着它不能位于&lt;f:metadata/&gt; 标记内。在这种情况下,该方法在所有视图参数都已设置并调用 searchBean.preRenderView(e) 后执行。我希望在调用任何页面的preRenderView(e) 之前调用userSessionBean.preRenderViewCookieLogin(e),而不是之后调用。只是为了好玩,我尝试在userSessionBean.preRenderViewCookieLogin(e) 周围放置一个&lt;f:metadata/&gt;,它调用了这个方法,但没有设置视图参数。

所以,我想知道:

  1. 为什么会发生这种情况?有办法解决吗?
  2. 有没有更好的方法来确保每个页面都先调用相同的方法?

编辑: 我只是尝试了其他方法-阶段事件: &lt;f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase(e)}"&gt; 这是在 masterLayout.xhtml 中。它根本没有被调用;不用于任何阶段。

编辑: 删除了 e(该死的 NetBeans!): &lt;f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase}"&gt; 这只是在渲染响应阶段之前调用,这当然意味着它是在引发preRenderView 事件之后调用的。

【问题讨论】:

    标签: jsf-2 facelets


    【解决方案1】:

    为什么会发生这种情况,有办法解决吗?

    来自&lt;f:metadata&gt; tag documentation(第二段的重点是我的):

    为此视图声明元数据方面。这必须是&lt;f:view&gt; 的子代。 此标记必须位于给定 viewId 的顶级 XHTML 文件中,或位于模板客户端中,但不能位于模板中。 实现必须确保 facet 的直接子级是 UIPanel ,即使刻面只有一个子级。实现必须将UIPanel 的id 设置为UIViewRoot.METADATA_FACET_NAME 符号常量的值。

    所以,它真的必须在顶视图中,而不是在模板中。


    有没有更好的方法来确保每个页面都先调用相同的方法?

    在您的特定情况下,将登录用户存储为会话范围托管 bean 的属性而不是 cookie,并在适当的 URL 模式上使用 filter 来检查它。会话范围的托管 bean 位于过滤器中,可用作 HttpSession 属性。本土 cookie 是不必要的,因为您基本上是在此处重新发明 HttpSession。除非你想要一个“记住我”的功能,但这不应该以这种方式解决。也可以在过滤器中进行。

    另见:

    【讨论】:

    • 感谢@BalusC。我实际上是在尝试实现“记住我”功能。根据会话范围的托管 bean 的状态,我希望每次首先检查用户是否已登录时调用的方法。这是我已经工作了一段时间的事情。我刚刚开始构建“记住我”功能,以便用户在会话到期后不必再次登录。在该页面的preRenderView 之前,是否没有一种通用方法可以为每个页面调用相同的方法?
    • 删除(e)。范围内没有这样的东西。
    • 完成。查看结果。我最终使用了相位监听器。阶段侦听器查找userSessionBean(会话范围)然后调用执行cookie 登录的bean 上的方法。我使用并投票赞成你的一些代码here 在阶段侦听器中按名称获取bean。我很快就会在这里发布解决方案。
    • 我没有选择过滤器选项,因为我认为它太过分了。因为我在 JSF 中管理所有身份验证,所以当一个简单的阶段侦听器可以解决问题时,在 JSF 之外处理其中的一些是没有意义的。我真的很感谢你的回答。哎呀,我给你这个,因为你确实回答了第一部分,并为我指出了解决第二部分的正确方向。
    【解决方案2】:

    我改用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 选项。不过没什么大不了的。

    【讨论】:

      猜你喜欢
      • 2014-11-16
      • 2012-03-06
      • 2011-08-04
      • 2011-06-13
      • 2012-06-19
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 2010-11-22
      相关资源
      最近更新 更多