如何在 JSF 1.x 中对页面加载进行重定向

Posted

技术标签:

【中文标题】如何在 JSF 1.x 中对页面加载进行重定向【英文标题】:How to make a redirection on page load in JSF 1.x 【发布时间】:2011-05-01 06:36:55 【问题描述】:

我有一个网络应用程序,可以将用户直接发送到某些特定页面(例如他可以查看或编辑项目的页面)。为此,我们提供了一个特定的网址。这些 url 位于当前网络应用程序外部(即它们可以存在于另一个网络应用程序或电子邮件中)。

网址看起来像http://myserver/my-app/forward.jsf?action=XXX&param=YYY,其中:

action 表示用户将被重定向到的页面。您可以将其视为navigation-casefaces-config.xml 中任何JSF 操作的from-outcomeactionParam 是上一个操作的参数(通常是项目 ID)

例如,我可以拥有这些类型的网址:

http://myserver/my-app/forward.jsf?action=viewItem&actionParam=1234 http://myserver/my-app/forward.jsf?action=editItem&actionParam=1234

当然,我有一个 Java 类(bean),它会检查一些安全约束(即用户是否允许查看/编辑相应的项目?)然后将用户重定向到正确的页面(例如edit.xhtml , view.xhtmlaccess-denied.xhtml)。


当前实施

目前,我们有一个基本的方法来完成转发。当用户单击该链接时,将调用以下 XHTML 页面:

<html>
    <body id="forwardForm">
        <h:inputHidden id="myAction" binding="#forwardBean.hiddenAction"/>
        <h:inputHidden id="myParam" binding="#forwardBean.hiddenActionParam"/>
        <h:commandButton id="forwardBtn" actionListener="#forwardBean.doForward" style="display: none;"/>
    </body>
    <script type="text/javascript">
        document.getElementById('forwardForm:forwardBtn').click();
    </script>
</html>

如您所见,我在我的 Java bean 中绑定了两个 &lt;h:inputHidden&gt; 组件。它们将用于存储actionactionParam 请求参数的值(使用FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("actiontParam");)。我还提供了doForward 方法,该方法将在页面呈现时立即调用,它将(再次)将用户重定向到真实页面。方法是:

public void doForward(ActionEvent evt) 
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String redirect = // define the navigation rule that must be used in order to redirect the user to the adequate page...
    NavigationHandler myNav = facesContext.getApplication().getNavigationHandler();
    myNav.handleNavigation(facesContext, null, redirect);


此解决方案有效,但我有两个问题:

我不喜欢它的实现方式。我确信我可以拥有更简单的东西(使用 Servlet?)。 此解决方案使用 Javascript,我不能使用 Javascript(因为此转发可能由不支持 Javascript 的老黑莓用​​户使用)。

所以我的问题是如何重构这个重定向/转发功能?

技术资料

Java 1.6、JSF 1.2、Facelets、Richfaces

【问题讨论】:

我不确定是否最好这样做,但你给用户一个点击链接并使用中间 xhtml 并制作 javascript 点击,但你为什么不将它指向一个 servlet 和从那里你可以处理这种情况,我的意思是为什么不使用 servlet 而不是中间 xhtml。我认为在 jsf env 中添加我们自己的 servlet 并不坏 @org:这当然可以在没有 servlet 的情况下完成。使用 servlet 只会增加额外的维护麻烦,因为它无法即时访问导航案例。 @BalusC 谢谢,我想知道,怎么做? , 可以直接在xhtml上使用jsp scriptlets吗? @org:我有一个理论,但我只发布我可以根据自己的经验判断它是正确的答案。而且我现在手头没有 JSF 1.2 操场环境。也许如果我有更多的时间。完全不鼓励使用 scriptlet。无论何种视图技术。 @org scriptlet 超出了我的词汇范围 ;) 事实上,如果我使用 Servlet 创建解决方案,我将不会受益于 JSF 的导航案例。 【参考方案1】:

假设 foo.jsp 是您的 jsp 文件。以下代码是您要重定向的按钮。

<h:commandButton value="Redirect" action="#trial.enter "/>  

现在我们将检查在您的 java(服务)类中的定向方法

 public String enter() 
            if (userName.equals("xyz") && password.equals("123")) 
                return "enter";
             else 
                return null;
            
         

现在这是 faces-config.xml 文件的一部分

<managed-bean>
        <managed-bean-name>'class_name'</managed-bean-name>
        <managed-bean-class>'package_name'</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>


    <navigation-case>
                <from-outcome>enter</from-outcome>
                <to-view-id>/foo.jsp</to-view-id>
                <redirect />
            </navigation-case>

【讨论】:

这并不能回答 OP 的真正要求。【参考方案2】:

你应该使用action而不是actionListener:

<h:commandLink id="close" action="#bean.close" value="Close" immediate="true" 
                                   />

并且在接近的方法中,您可以正确地使用以下内容:

public String close() 
   return "index?faces-redirect=true";

其中 index 是您的页面之一(index.xhtml)

当然,所有这些工作人员都应该写在我们的原始页面中,而不是中间页面。 在close() 方法中,您可以使用参数动态选择重定向的位置。

【讨论】:

使用actionactionListener 对我当前的问题绝对没有影响。 OP 使用的是 JSF 1.x,而不是 2.x。另外,重定向会导致请求参数丢失。【参考方案3】:

将 GET 查询参数设置为 faces-config.xml 中的托管属性,这样您就不需要手动收集它们:

<managed-bean>
    <managed-bean-name>forward</managed-bean-name>
    <managed-bean-class>com.example.ForwardBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>action</property-name>
        <value>#param.action</value>
    </managed-property>
    <managed-property>
        <property-name>actionParam</property-name>
        <value>#param.actionParam</value>
    </managed-property>
</managed-bean>

这样,请求forward.jsf?action=outcome1&amp;actionParam=123 将让JSF 将actionactionParam 参数设置为actionactionParamForwardBean 属性。

创建一个小视图forward.xhtml(小到可以放入默认响应缓冲区(通常为 2KB),以便导航处理程序将其重置,否则您必须在 servletcontainer 的配置中增加响应缓冲区),这在f:viewbeforePhase 上调用一个bean 方法:

<!DOCTYPE html>
<html xmlns:f="http://java.sun.com/jsf/core">
    <f:view beforePhase="#forward.navigate" />
</html>

ForwardBean 可能如下所示:

public class ForwardBean 
    private String action;
    private String actionParam;

    public void navigate(PhaseEvent event) 
        FacesContext facesContext = FacesContext.getCurrentInstance();
        String outcome = action; // Do your thing?
        facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome);
    

    // Add/generate the usual boilerplate.

navigation-rule 不言自明(注意 &lt;redirect /&gt; 条目会在封面下使用 ExternalContext#redirect() 而不是 ExternalContext#dispatch()):

<navigation-rule>
    <navigation-case>
        <from-outcome>outcome1</from-outcome>
        <to-view-id>/outcome1.xhtml</to-view-id>
        <redirect />
    </navigation-case>
    <navigation-case>
        <from-outcome>outcome2</from-outcome>
        <to-view-id>/outcome2.xhtml</to-view-id>
        <redirect />
    </navigation-case>
</navigation-rule>

另一种方法是使用forward.xhtml as

<!DOCTYPE html>
<html>#forward</html>

并更新要在@PostConstruct 上调用的navigate() 方法(将在bean 的构造和所有托管属性设置后调用):

@PostConstruct
public void navigate() 
    // ...
    

它具有相同的效果,但是视图端并不是真正的自我记录。它基本上所做的只是打印ForwardBean#toString()(如果还没有,则隐式构造bean)。


请注意,对于 JSF2 用户,&lt;f:viewParam&gt; 传递参数的方式更简洁,&lt;f:event type="preRenderView"&gt; 处理重定向/导航的方式更稳健。另见:

Hit a bean method and redirect on a GET request Is there any easy way to preprocess and redirect GET requests?

【讨论】:

我喜欢你使用&lt;f:view beforePhase=#..."/&gt; 的想法。但是,我的第一个测试没有成功,因为没有调用 beforePhase 方法。我不明白为什么...... 我仍然不明白为什么第一个解决方案不起作用(也许我会为此创建一个单独的帖子),但第二个解决方案确实有效!非常感谢! PhaseEvent 参数是否存在?究竟是什么 JSF impl/version?我必须承认我在 2.1-nightly 上测试了 faces-config 声明为 1.2(这样 2.x 功能将无法工作)。顺便说一句,该属性是在 1.2 中引入的(因此在 1.1 或更早版本上根本不起作用)。但@PostConstruct也是如此。【参考方案4】:

编辑 2

我终于通过执行我的向前操作找到了解决方案:

private void applyForward() 
    FacesContext facesContext = FacesContext.getCurrentInstance();
    // Find where to redirect the user.
    String redirect = getTheFromOutCome();

    // Change the Navigation context.
    NavigationHandler myNav = facesContext.getApplication().getNavigationHandler();
    myNav.handleNavigation(facesContext, null, redirect);

    // Update the view root
    UIViewRoot vr = facesContext.getViewRoot();
    if (vr != null) 
        // Get the URL where to redirect the user
        String url = facesContext.getExternalContext().getRequestContextPath();
        url = url + "/" + vr.getViewId().replace(".xhtml", ".jsf");
        Object obj = facesContext.getExternalContext().getResponse();
        if (obj instanceof HttpServletResponse) 
            HttpServletResponse response = (HttpServletResponse) obj;
            try 
                // Redirect the user now.
                response.sendRedirect(response.encodeURL(url));
             catch (IOException e) 
                e.printStackTrace();
            
        
    

它可以工作(至少关于我的第一次测试),但我仍然不喜欢它的实现方式......有更好的主意吗?


编辑此解决方案不起作用。确实,在调用doForward()函数的时候,JSF生命周期已经开始了,再重新创建一个新的请求是不可能的。


解决此问题的一个想法,但我不太喜欢,是在 setBindedInputHidden() 方法之一期间强制执行 doForward() 操作:

private boolean actionDefined = false;
private boolean actionParamDefined = false;

public void setHiddenActionParam(HtmlInputHidden hiddenActionParam) 
    this.hiddenActionParam = hiddenActionParam;
    String actionParam = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("actionParam");
    this.hiddenActionParam.setValue(actionParam);
    actionParamDefined = true;
    forwardAction();


public void setHiddenAction(HtmlInputHidden hiddenAction) 
    this.hiddenAction = hiddenAction;
    String action = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("action");
    this.hiddenAction.setValue(action);
    actionDefined = true;
    forwardAction();


private void forwardAction() 
    if (!actionDefined || !actionParamDefined) 
        // As one of the inputHidden was not binded yet, we do nothing...
        return;
    
    // Now, both action and actionParam inputHidden are binded, we can execute the forward...
    doForward(null);

此解决方案不涉及任何 Javascript 调用,有效有效。

【讨论】:

这确实很尴尬:) handleNavigation() 在这里完全没有必要(它又被response.sendRedirect() 覆盖)并且ExternalContext 有一个redirect() 方法。【参考方案5】:
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();
response.sendRedirect("somePage.jsp");

【讨论】:

不回答我的问题。你想让我把这段代码放在哪里?以及如何在没有任何用户交互或 Javascript 调用的情况下调用它?

以上是关于如何在 JSF 1.x 中对页面加载进行重定向的主要内容,如果未能解决你的问题,请参考以下文章

JSF 如何从支持 bean 重定向到支持 bean 中动态构建的 URL?

会话范围和 jsf 重定向

h:commandbutton,如何重定向到外部站点?(JSF 2)[重复]

如何在 JSF 2.0 中重定向

通过 JSF 表单成功登录后 Spring Security 不会重定向到登录页面

AngularJS - 如何在整个页面加载时进行重定向?