如何在运行时向@SessionScoped bean提供@RequestScoped bean实例?

时间:2014-10-10 17:54:41

标签: jsf java-ee ejb cdi managed-bean

我正在JBoss中阅读此示例,其中使用@RequestScoped备份JSF page的bean来传递用户凭据信息,然后将其保存在@sessionScoped bean中。 以下是JBoss docs.

的示例
@Named @RequestScoped
public class Credentials {
    private String username;
    private String password;
    @NotNull @Length(min=3, max=25)
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    @NotNull @Length(min=6, max=20)
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

JSF表格:

<h:form>
   <h:panelGrid columns="2" rendered="#{!login.loggedIn}">
      <f:validateBean>
         <h:outputLabel for="username">Username:</h:outputLabel>
         <h:inputText id="username" value="#{credentials.username}"/>
         <h:outputLabel for="password">Password:</h:outputLabel>
         <h:inputSecret id="password" value="#{credentials.password}"/>
      </f:validateBean>
   </h:panelGrid>
   <h:commandButton value="Login" action="#{login.login}" rendered="#{!login.loggedIn}"/>
   <h:commandButton value="Logout" action="#{login.logout}" rendered="#{login.loggedIn}"/>
</h:form>

用户实体:

@Entity
public class User {
   private @NotNull @Length(min=3, max=25) @Id String username;
   private @NotNull @Length(min=6, max=20) String password;
   public String getUsername() { return username; }
   public void setUsername(String username) { this.username = username; }
   public String setPassword(String password) { this.password = password; }
}

SessionScoped bean

@SessionScoped @Named
    public class Login implements Serializable {

   @Inject Credentials credentials;
   @Inject @UserDatabase EntityManager userDatabase;
   private User user;
   public void login() {
      List<User> results = userDatabase.createQuery(
         "select u from User u where u.username = :username and u.password = :password")
         .setParameter("username", credentials.getUsername())
         .setParameter("password", credentials.getPassword())
         .getResultList();
      if (!results.isEmpty()) {
         user = results.get(0);
      }
      else {
         // perhaps add code here to report a failed login
      }
   }

   public void logout() {
      user = null;
   }

   public boolean isLoggedIn() {
      return user != null;
   }

   @Produces @LoggedIn User getCurrentUser() {
 return user;
   }
}

我的问题是

1)@RequestScoped bean被注入@SessionScoped bean。在RequestScoped的一个实例上设置的凭证信息与注入@SessionScoped bean的相同的保证是什么。为什么不注入池中不同的@RequestScoped甚至是新实例?

2)为什么bean被赋予@SessionScoped而不是@Stateful。我想@Stateful可以在这里工作。

3)如何管理@sessionScoped bean的生命周期?那是什么时候被摧毁了?如果我导航到另一个JSF页面,如果我提取currentUser.userName等信息,我将检索我在用于登录的第一个JSF页面上设置的相同信息。(上面的第1步)

4)如果我没有指定@RequestScoped,那么Credentials bean将获得作为defualt范围的@Dependent范围。在docs中提到,设置@Dependent的任何实例变量会立即丢失。但我不明白为什么?事实上,这提示我@Dependent范围的用途是什么?

由于

修改 感谢kolossus提供详细而优秀的答案。为了更好地理解,我需要对你的一些要点做一些澄清

  1. 对于@requestScoped bean,有一个可用的实例池可以移交给客户端。现在,如果我有两个客户端访问由@RequestScoped bean支持的JSF,则每个客户端都可以从池中处理一个@RequestScoped bean实例。实际上,两个客户端实际上并不在直接实例上工作,而是间接引用作为代理的单个实例。客户端使用此代理执行所有方法调用或事务。所以代理持有这个间接引用多长时间?也就是说,在上面的示例中,@RequestScoped bean(Credentials)的实例变量在JSF中设置。但事实是,实例变量的这种设置通过代理间接发生在@RequestScoped bean的一个实例上。但是当这个实例被注入SessionScoped bean时,它是被注入的代理吗?由于SessionScoped的生命周期适用于在客户端和应用程序之间建立的会话,因此代理也会在此生命周期内生效。这是否意味着此single instance of @RequestScoped bean绑定到SessionScoped并且@RequestScoped bean实例或其代理的生命周期由SessionScoped bean的生命周期确定?

1 个答案:

答案 0 :(得分:8)

  1.   

    @RequestScoped bean被注入@SessionScoped bean。在RequestScoped的一个实例上设置的凭据信息与注入@SessionScopedbean的凭证信息相同的保证是什么。为什么不注入池中不同的@RequestScoped甚至是新实例?

    这是合法的,这要归功于CDI实际获取对所请求bean的引用的方式:client proxies。来自the CDI spec

      
        

    通过编程查找获得的注入引用或引用通常是上下文引用。对具有正常范围的bean的上下文引用[...]不是对a的直接引用bean的上下文实例[...]而是上下文引用是客户端代理对象     客户端代理实现/扩展bean的部分或全部bean类型,并将所有方法调用委托给bean的当前实例 ...

      
         

    这种间接的原因有很多:

         
        
    • 容器必须保证在调用对正常范围的bean的任何有效注入引用时,调用始终由注入bean的当前实例处理。在某些情况下,例如,如果请求范围bean被注入到会话范围的bean或servlet中,则此规则需要间接引用
    •   

    同样来自this DZone CDI article

      

    CDI通过使用代理来处理具有不匹配范围的bean的注入。因此,您可以将请求范围的bean注入到会话范围的bean中,并且引用在每个请求上仍然有效,因为对于每个请求,代理重新连接到请求范围bean的实时实例

    这意味着,代理在每个注入点代替真实的东西。代理通过扩展/实现它应该模仿的类型的祖先树来模仿注入点处声明的类型。在您现在实际需要使用该对象时,代理会对当前对话中请求的bean的现有实例执行基于上下文的查找。这是一个请求范围的对象,您可以确保在当前对话/上下文中只有一个实例。

  2.   

    为什么bean被赋予@SessionScoped而不是@Stateful。我想@Stateful会在这里工作。

    @Stateful在这里不起作用,就像我说here一样,它们并不便宜;除非你真的需要,坚持使用香草HttpSession。更不用说一旦SFSB的客户端释放了它被销毁的bean,即SFSB与当前会话无关,@SessionScoped就是。

  3.   

    @sessionScoped bean的生命周期是如何管理的?那是什么时候被摧毁了?如果我导航到另一个JSF页面,如果我提取诸如currentUser.userName之类的信息,我将检索我在用于登录的第一个JSF页面上设置的相同信息。(上面的步骤1)

    取决于您@SessionScoped引用的javax.faces.bean.SessionScopedHttpSession与当前的@Dependent /浏览器会话直接相关联,因此只要死亡,它就会终止;然而JBoss implies that javax.enterprise.context.* scoped beans don't actually go anywhere until the "context" dies

      

    在整个上下文被销毁之前,实际上无法从上下文中删除bean

  4. @Inject @New Login aDependentLoginBean; //implicit @Dependent scope applied @Inject Login aSessionScopedLoginBean; //standard Login bean's scope applied 视为任何方法局部变量:它只有在它的父结构存在的情况下才有用。话虽这么说,它的最佳用途不是支持JSF视图。它最有用的应用程序正在覆盖bean在ad-hoc上指定的范围。使用您当前的示例,我可以在我的应用程序中的其他位置使用以下内容:

    @Dependent

    @New一起,您可以将任何其他bean重新用于{{1}}


  5. 相关: