2

编辑 4

我想做的是实现忘记密码页面。例如,我采取了以下示例,这不是真正的用户相关问题,我将用户名保留在会话范围内。

index.xhtml将是忘记密码页面,我将在其中输入用户名。输入用户名后,我将单击Welcome Me - Action并在 中chkMe(),我将检查该用户并在他/她的电子邮件 ID 和 Welcome.xhtml 中发送新密码,我会说Hi User ABC, we have sent new password at asdfasdf@dasf.com


主帖

我正在尝试用两种情况将数据从一个 bean 打印到另一个。下面是我的代码。

索引.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"      
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>JSF 2.0 Hello World</title>
    </h:head>
    <h:body>
        <h3>JSF 2.0 Hello World Example - hello.xhtml</h3>
        <h:form>
           <h:inputText value="#{helloBean.name}"></h:inputText>
           <h:commandButton value="Welcome Me - Plain" action="welcome"></h:commandButton>
           <h:commandButton value="Welcome Me - Action" action="#{helloBean.chkMe()}"></h:commandButton>
        </h:form>
    </h:body>
</html>

欢迎.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"    
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>JSF 2.0 Hello World</title>
    </h:head>
    <h:body bgcolor="white">
        <h3>JSF 2.0 Hello World Example - welcome.xhtml</h3>
        <h4>Welcome --#{helloBean.name}--</h4>
    </h:body>
</html>

HelloBean.java

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import java.io.Serializable;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class HelloBean implements Serializable {

    private static final long serialVersionUID = 1L;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String chkMe() {
        return takeMeToAnotherPage("welcome");
    }

    public String takeMeToAnotherPage(String linkToGo) {
        return linkToGo + "?faces-redirect=true";
    }
}

当我在文本Checking字段中输入文本并单击按钮时,我在 welcome.xhtml 中Welcome Me - Plain看到文本为文本,但是当我单击时,我看不到任何文本(我看到为)Welcome --Checking--Welcome Me - ActionWelcome ----

我不知道为什么会这样。

任何想法/建议为什么会发生这种情况。


编辑 1

我相信这都是因为?faces-redirect=true,但我必须像不使用一样使用它?faces-redirect=true,地址栏中的 URL 是以前的 url。

例如,如果我在 page1.xhtml 上,然后转到 page2.xhtml,URL 仍然会显示 page1.xhtml。

所以不知道在这种情况下该怎么办。


编辑 2

好吧,我真正想做的是忘记密码页面,我将在 index.xhtml 中输入用户名(考虑上面的示例),如果该用户名是正确的,在 welcome.xhtml 上,我将拥有Hi User ABC, Please use new password for next login. We have sent you email at blah@blah.com.

RequestScope工作正常,但问题出在 URL 地址上,因此我添加了?faces-redirect=true. 但是随着它的重定向,http会话正在关闭,因此在welcome.xhtml上,我没有得到任何价值(这就是上面发生的事情)。

另一个解决方案skuntsel是使用FlashScope但问题是当我刷新 welcome.xhtml 时,数据消失了这让我抓狂。

任何人都可以建议需要做什么?


编辑 3

会话范围内的问题如下。

考虑我打开两个选项卡,在两个选项卡上我都有 index.xhtml。在 tab1 我输入Fahim并单击Welcome Me - Action。在 tab1 上,welcome.xhtml 出现,我看到文本为Welcome Fahim. 太棒了。

现在我来到 tab2,输入 name as XYZ,然后单击Welcome Me - ActionI get welcome.xhtml ,我看到 text as Welcome XYZ这也是完美的。

问题是当我回到 tab1 并刷新页面时。当我刷新 tab1 (welcome.xhtml) 时,我看到Welcome XYZ之前的错误,Welcome Fahim应该是Welcome Fahim.

4

8 回答 8

2

在我看来,在会话范围内使用当前用户是个好主意。

不过,如果它不适合您,我可以提供更多替代方案。

将用户名作为视图参数传递

这转化为

<h:form>
    <h:inputText value="#{helloBean.name}"/>
    <h:commandButton value="Welcome Me - Action" action="#{helloBean.chkMe}"/>
</h:form>

public String chkMe() {
    return takeMeToAnotherPage("welcome");
}

public String takeMeToAnotherPage(String linkToGo) {
    return linkToGo + "?faces-redirect=true&username=" + name;
}

和额外的视图参数welcome.xhtml

<f:metadata>
    <f:viewParam name="username"/>
</f:metadata>

另一种选择是及时实例化另一个请求范围的bean并将信息传递给它

<h:form>
   <h:inputText value="#{helloBean.name}"/>
   <h:commandButton value="Welcome Me - Plain" action="welcome">
       <f:setPropertyActionListener value="#{helloBean.name}" target="#{welcomePageBean.username}"/>
   </h:commandButton>
</h:form>

@ManagedBean
@RequestScoped
WelcomePageBean {

    private String username;//+getter + setter
    //other fields associated with the welcome view

}

使用 Flash 对象

详细条目视图(片段)base.xhtml,:

<h:form>
    <h:outputText value="Enter user name for password reset: " />
    <h:inputText value="#{flash.username}" />
    <br/>
    <h:commandButton value="Send me a confirmation email" action="#{forgotBean.changePassword}" />
<h:form>

ForgotBeanbase.xhtml

@ManagedBean
@RequestScoped
public class ForgotBean {

    public ForgotBean() {   }

    public String changePassword() {
        //check user constraints and return failure outcome in case somthing is wrong
        //generate new password and persist it to the database
        //send a configmation e-mail
        return "successful-reset?faces-redirect=true";
    }

}

成功视图(片段)successful-reset.xhtml,:

<h:outputText value="Password was reset for user #{receptorBean.username}, e-mail configmation sent." />
<br/>
<h:link value="View homepage" outcome="home" />

ReceptorBeansuccessful-reset.xhtml

@ManagedBean
@RequestScoped
public class ReceptorBean {

    @ManagedProperty("#{flash}")
    private Flash flash;

    private String username;

    public ReceptorBean() {   }

    public String getUsername() {
        if(username == null) {
            String uname = (String)flash.get("username");
            flash.keep("inputText");
            username= uname;
        }
        return username;
    }

    public Flash getFlash() {
        return flash;
    }

    public void setFlash(Flash flash) {
        this.flash = flash;
    }

}
于 2013-02-23T07:59:20.777 回答
1

我想 faces-redirect=true 做了一个 http 重定向,插入了一个 http 转发。重定向由浏览器处理,浏览器发送一个新的 http 请求。由于您使用的是 RequestScoped Mbean,因此新请求将使用新的 MBean 呈现。因此,请使用 SessionScoped Mbean 或在不进行重定向的情况下导航。

于 2013-02-23T07:06:09.123 回答
1

当您刷新页面时,您正在向服务器提交新请求。由于 HTTP 协议是无状态的,因此您可以通过将其添加到 URL 中来告诉服务器我是“user1”,因为该 URL 在选项卡中是唯一的。

如果您不想在 URL 中添加参数,服务器将使用 session 来维护两个请求之间的关系。然后,您可以使用会话来存储变量等。

在您的情况下,它不起作用,因为您不想在选项卡之间共享数据。数据在选项卡之间共享的原因是会话 cookie 在选项卡上共享。

您需要禁用 cookie 并使用 URL 重写来维护会话。

http://docstore.mik.ua/orelly/java-ent/servlet/ch07_03.htm

您正在使用的应用程序服务器应在 jsf 中维护 URL,并附加 sessionid,但是在重定向时,您可能需要将会话 ID 附加到您的 URL。

  public String takeMeToAnotherPage(String linkToGo) {
           HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().
getExternalContext().getRequest(); 
           return linkToGo + "?faces-redirect=true&jsessionid=" 
+ req.getSession().getId();
        }

由于您的应用程序在多个选项卡上使用,您可以禁用会话 cookie 并使用 url 重写来维护会话。

http://jf.omnis.ch/archives/2004/12/disabling-session-cookie-in-tomcat.html

https://community.jboss.org/thread/141685

另一种选择是使用对话范围,只要用户访问欢迎页面,您就可以在其中开始新对话。您的应用程序将在两个选项卡中维护两个不同的对话。您可以在此处了解更多信息,

http://www.andygibson.net/blog/tutorial/cdi-conversations-part-2/

于 2013-02-28T20:30:29.580 回答
1

将此代码添加到正文标记下方的索引页

<t:saveState value="#{helloBean}" />

t tag库使用

<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

并添加战斧库。

如果您不知道保存状态或参数传递,请参阅此链接

这将帮助您传递价值的基本步骤。jsf中的页面之间。

于 2013-02-26T10:09:42.723 回答
1

因此,也许您的 bean 是 @RequestScoped 。将其更改为会话范围的 bean。

于 2013-02-23T07:10:09.273 回答
1

使用 Flash 对象的示例

我实际上不明白什么不起作用。下面是一个main-dependent视图的工作示例,其中第一个视图上的命令按钮触发重定向到依赖视图并使用 JSFFlash对象处理数据传输。在提供的答案中,第二个页面数据 ( username)在回发和页面刷新操作中都存在

我将再次重新发布代码。

主视图:

<h:form>
    <h:outputText value="Enter your name: " />
    <h:inputText value="#{flash.username}" />
    <h:commandButton value="Reset my password" action="#{forgotBean.changePassword}" />
</h:form>

忘记豆:

@ManagedBean
@RequestScoped
public class ForgotBean {

    public ForgotBean() {   }

    public String changePassword() {
        return "/dependent?faces-redirect=true";
    }

}

依赖视图:

<h:form>
   <h:outputText value="#{confirmBean.username}: your password has been changed and the confirmation has been sent to [get value from your bean]" />
   <h:commandButton value="Postback" />
</h:form>

确认豆:

@ManagedBean
@RequestScoped
public class ConfirmBean {

    @ManagedProperty("#{flash}")
    private Flash flash;

    private String username;

    public ReceivedBean() {   }

    public String getUsername() {
        if(username == null) {
            String uname = (String)flash.get("username");
            flash.keep("username");
            username = uname;
        }
        return username;
    }

    public Flash getFlash() {
        return flash;
    }

    public void setFlash(Flash flash) {
        this.flash = flash;
    }

}

或者,您也可以@PostConstructConfirmBean. 此外,您可以这样做,@ViewScoped以便在回发时不会发生预处理。

您的@PostConstruct方法的启动示例:

@PostConstruct
public void init() {
    //without managed property flash object is also available via
    //FacesContext.getCurrentInstance().getExternalContext().getFlash()
    String name = flash.get("username");
    flash.keep("username");
    //do necessary validations
    //get necessary data from your service
    //handle wrong user input
    this.username = name;
    //set up other data of the bean
    //change setter and getter of the field username to 'ordinary'
}

使用会话范围 bean 的示例

如果您希望 bean 在页面刷新后仍然存在(因此@ViewScoped失败),如果您想实现 Post-Redirect-Get(因此<f:setPropertyActionListener>失败),如果您想处理基本相同的数据,这实际上是未定义的,在不同的选项卡(因此Flash失败),如果您想阻止有意义的获取请求(因此<f:viewParam>失败),那么我看到的唯一方法(留在 JSF 中)是使用@SessionScoped @ManagedBean将用户输入数据保留在集合中的. 这样,页面刷新发、重定向多标签有意义的视图参数限制已放宽,并将在您的应用程序中启用。

请注意,提供的代码不考虑任何异常处理/检查。而且,更重要和更相关的是,作为映射中键的递增整数应该更好地替代随机值,例如UUID

意见(m 表示索引,d 表示欢迎):

( m.xhtml):

<h:body>
    <h:form>
        <h:inputText value="#{forgotBean2.username}" />
        <h:commandButton value="Change my password" action="#{forgotBean2.changePassword}" />
    </h:form>
</h:body>

( d.xhtml):

<f:metadata>
    <f:viewParam name="id" required="true" />
</f:metadata>
<h:body>
    <h:form>
        <h:outputText value="#{confirmBean2.username}: your password has been changed" />
        <h:commandButton value="Postback" action ="#{confirmBean2.postbackAction}" />
    </h:form>
</h:body>

bean(ForgotBean2- 用于业务操作m.xhtmlConfirmBean2- 用于显示和(可能的)操作d.xhtmlUserRequestsBean- 用于在会话中存储信息):

( ForgotBean2):

@ManagedBean
@RequestScoped
public class ForgotBean2 {

    @ManagedProperty("#{userRequestsBean}")
    private UserRequestsBean userRequestsBean;

    private String username;

    public ForgotBean2() {   }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public UserRequestsBean getUserRequestsBean() {
        return userRequestsBean;
    }

    public void setUserRequestsBean(UserRequestsBean userRequestsBean) {
        this.userRequestsBean = userRequestsBean;
    }

    public String changePassword() {
        //do business job
        Map<Integer, String> fMap = userRequestsBean.getRequests();
        int id = 0;
        if(fMap.containsValue(username)) {
            for(Map.Entry<Integer, String> entry : fMap.entrySet()) {
                if(entry.getValue().equals(username)) {
                    id = entry.getKey();
                    break;
                }
            }
        } else {
            if(fMap.isEmpty()) {
                id = 1;
            } else {
                id = (int)Collections.max(fMap.keySet()) + 1;
            }
            fMap.put(id, username);
        }
        return "/q15038451/d?faces-redirect=true&id=" + Integer.toString(id);
    }

}

( ConfirmBean2):

@ManagedBean
@ViewScoped
public class ConfirmBean2 implements Serializable {

    @ManagedProperty("#{userRequestsBean}")
    private UserRequestsBean userRequestsBean;

    private Integer id;

    private String username;

    public ConfirmBean2() {   }

    @PostConstruct
    public void init() {
        if(id == null) {
            String vpid = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("id");
            id = Integer.parseInt(vpid);
        }
        Map<Integer, String> fMap = userRequestsBean.getRequests();
        String uname = fMap.get(id);
        //do necessary validations
        //get necessary data from your service
        //handle wrong user input
        this.username = uname;
        //set up other data of the bean
    }

    public UserRequestsBean getUserRequestsBean() {
        return userRequestsBean;
    }

    public void setUserRequestsBean(UserRequestsBean userRequestsBean) {
        this.userRequestsBean = userRequestsBean;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String postbackAction() {
        return null;
    }

}

( UserRequestsBean):

@ManagedBean
@SessionScoped
public class UserRequestsBean implements Serializable {

    private Map<Integer, String> requests = new HashMap<Integer, String>();

    public UserRequestsBean() {   }

    public Map<Integer, String> getRequests() {
        return requests;
    }

    public void setRequests(Map<Integer, String> requests) {
        this.requests = requests;
    }

}

如上所述,您可能想要生成UUIDs(提供唯一和随机值)而不是递增Integer的 s。

于 2013-02-27T08:27:26.573 回答
1

当您刷新页面时,您正在向服务器提交新请求。由于 HTTP 协议是无状态的,因此您可以通过将其添加到 URL 中来告诉服务器我是“user1”,因为该 URL 在选项卡中是唯一的。

如果您不想在 URL 中添加参数,服务器将使用 session 来维护两个请求之间的关系。然后,您可以使用会话来存储变量等。

在您的情况下,它不起作用,因为您不想在选项卡之间共享数据。数据在选项卡之间共享的原因是会话 cookie 在选项卡上共享。

您需要禁用 cookie 并使用 URL 重写来维护会话。

http://docstore.mik.ua/orelly/java-ent/servlet/ch07_03.htm

您正在使用的应用程序服务器应该在 jsf 中维护 URL,并附加 sessionid,但是在重定向时,您可能需要将会话 ID 附加到您的 URL。

    public String takeMeToAnotherPage(String linkToGo) {
           HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().
getExternalContext().getRequest(); 
           return linkToGo + "?faces-redirect=true&jsessionid=" 
+ req.getSession().getId();
        }

由于您的应用程序在多个选项卡上使用,您可以禁用会话 cookie 并使用 url 重写来维护会话。

http://jf.omnis.ch/archives/2004/12/disabling-session-cookie-in-tomcat.html

https://community.jboss.org/thread/141685

另一种选择是使用对话范围,只要用户访问欢迎页面,您就可以在其中开始新对话。您的应用程序将在两个选项卡中维护两个不同的对话。您可以在此处了解更多信息,

http://www.andygibson.net/blog/tutorial/cdi-conversations-part-2/

于 2013-02-28T02:38:48.777 回答
1

让我们从 JSF 中抽象出来,从 HTTP 级别分析您的需求。

URL 必须更改为 welcome.xhtml

这意味着当单击按钮时,必须向服务器发送两个请求:

POST index.xhtml (with username parameter in the request body)- 将用户的输入提交给服务器

GET welcome.xhtml- 获取新页面

当服务器处理GET welcome.xhtml请求时,有两种获取数据的方式:

  • 从客户端的请求中获取数据(通过 GET 请求传递数据的唯一方法是将其放到 URL 中)
  • 让它在服务器上可用(我们应该将它保存在 'POST index.xhtml' 处理中)

页面必须在刷新后存活

刷新只是上次请求的重复。这意味着我们正在重复GET welcome.xhtml请求。同样,我们有两种获取数据的方法(从 url 和从服务器)。

URL 不得包含数据(用户名)

这意味着无法从客户端的请求中检索数据。因此,获取数据的唯一方法是在“POST index.xhtml”处理期间首先将其存储在服务器上。

具有不同数据的多个选项卡

糟糕,目前唯一幸存下来的解决方案无法满足最后一个要求。由于选项卡共享相同的浏览器会话(相同的 cookie 等),因此无法区分来自不同选项卡的请求。因此来自不同选项卡的请求看起来完全相同,无法确定用户是刷新页面还是复制了 url 并打开了新选项卡。

结论

如果您提出严格的要求 - 就不可能实现您想要的(这不是 JSF 的限制,它是 HTTP 的工作方式)。所以你必须放宽要求。

删除多个选项卡支持 - 您可以使用 Session 范围。

删除生存刷新 - 您可以使用 Flash 范围。


最有趣的是放宽了不要在 url 中传递数据的要求。

最简单的方法是按原样传递用户名参数。您可能会说这是一个安全风险,但我会说风险与在POST index.xhtml请求中传递用户名几乎相同(只是传递参数的方式有所不同)。我看到的唯一额外的安全风险是有人可以通过用户的计算机并在浏览器的 url 栏中窥探用户名。

更高级的方法是加密用户名并在 URL 中传递加密值,并在处理welcome.xhtml 时对其进行解密。

如果我们进一步开发这种方法,我们将接近所谓的对话范围(查看 JBoss Seam 长时间运行的对话)。当您向 URL 添加无意义(从用户角度)参数时,该参数的值是一些标识符,它充当服务器上数据映射的键。

对话范围的虚拟实现:

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

@ManagedBean
@SessionScoped
public class Bean implements Serializable {

    private int prevCid = 0;

    private Map<String, Object> conversationScope = new HashMap<String, Object>();

    public String getName() {
        return (String) getData();
    }

    public void setName(String name) {
        setData(name);
    }

    public String chkMe() {
        return takeMeToAnotherPage("welcome");
    }

    public String takeMeToAnotherPage(String linkToGo) {
        return linkToGo + "?cid=" + getCid() + "&faces-redirect=true";
    }

    private Object getData() {
        return conversationScope.get(getCid());
    }

    private void setData(Object o) {
        conversationScope.put(getCid(), o);
    }

    private String getCid() {
        HttpServletRequest req = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
        String cid = (String) req.getAttribute("cid");

        if (cid == null) {
            cid = req.getParameter("cid");
            if (cid == null) {
              cid = "" + prevCid++;
            }
            req.setAttribute("cid", cid);
        }

        return cid;
    }
}
于 2013-02-27T09:37:50.307 回答