how is the @RequestScoped bean instance provided to @SessionScoped bean in runtime here?
Asked Answered
L

1

7

I am reading through this example in JBoss where a @RequestScoped bean backing up JSF page is used to pass the user credential information which is then saved in a @sessionScoped bean. Here is the example take from 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 form:

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

User Entity:

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

My questions are

1) The @RequestScoped bean gets injected into @SessionScoped bean. What is the guarantee that the credential information set on one instance of RequestScoped is the same that is injected into @SessionScopedbean. why not a different @RequestScoped from pool gets injected or even a new instance?

2)why is the bean given @SessionScoped but not @Stateful. I guess @Stateful will work here.

3)how is the lifecycle of @sessionScoped bean managed? That is when does it gets destroyed ?. If I navigate to a different JSF page in which if I pull the information such as currentUser.userName, will I retrieve the same information I set on my first JSF page used to log in. (step 1 above)

4) If I don't specify @RequestScoped, then the Credentials bean get the @Dependent scope which is the defualt scope. It is mentioned in the docs that setting any instance variables of a @Dependent gets lost immediately. But I don't understand why? In fact, this prompts me the question of what use of @Dependent scope will be ?

Thanks

EDIT Thanks kolossus for detailed and excellent answer. I need a little more clarifications on some of your points for better understanding

  1. For a @requestScoped bean, there are is a pool of instances available which gets handed over to clients. Now if I have two clients accessing a JSF which is backed by a @RequestScoped bean, each client gets to work on one instance of @RequestScoped bean from the pool. In fact, both the clients do not actually work on the direct instance, but an indirect reference to the that single instance which is the proxy here. clients do all method calls or transactions using this proxy. so how long does the proxy holds this indirect reference? That is, in my example above, instance variables of @RequestScoped bean (Credentials) are set in JSF. but the true fact is that, this setting of instance variables happen to one instance of @RequestScoped bean indirectly through proxy. But when this instance is injected into SessionScoped bean, is it the proxy that gets injected? Since the lifecycle of SessionScoped is for a session established between client and application, does the proxy also live during this lifetime. Does that mean this single instance of @RequestScoped bean is bound to SessionScoped and the lifecycle of @RequestScoped bean instance or its proxy is determined by the lifecycle of SessionScoped bean?
Lempira answered 10/10, 2014 at 17:54 Comment(7)
I doubt that this is even legal. You can inject variables from longer-lived scopes, in this case Application, but jot from shorter-lived ones.Sidhu
@EJP: The above code is from JBoss documentation which I have linked above. In fact, I had similar question whether it is legal or not just posted here: #26285916Lempira
Interesting. It's illegal in JSF itself.Sidhu
Your edit is not clear and it has too many meta-questionsClemmie
You're conflating the EJB mechanism with the CDI scoping mechanism: @RequestScoped beans are not pooled. Why would they be? They're alive only for the duration of a single request. A user would need only one.Clemmie
@kolossus: one final thought. what does it mean to have @RequestScoped injected into @SessionScoped? is the lifecycle of that instance of @RequestScoped is bound by @SessionScoped?Lempira
There's an invocation context associated with almost everything in CDI (that's what is meant by "contextual reference" in my answer). Think of it as a conversation: for every conversation/context, there's going to be one of each type of bean. Given this context, a @SessionScoped bean is able to claim the @RequestScoped bean and vice versaClemmie
C
8
  1. The @RequestScoped bean gets injected into @SessionScoped bean. What is the guarantee that the credential information set on one instance of RequestScoped is the same that is injected into @SessionScopedbean. why not a different @RequestScoped from pool gets injected or even a new instance?

    This is legal, thanks to the means by which CDI actually obtains references to a requested bean: client proxies. From the CDI spec

    An injected reference, or reference obtained by programmatic lookup, is usually a contextual reference.A contextual reference to a bean with a normal scope[...], is not a direct reference to a contextual instance of the bean[...].Instead, the contextual reference is a client proxy object A client proxy implements/extends some or all of the bean types of the bean and delegates all method calls to the current instance of the bean...

    There are a number of reasons for this indirection:

    • The container must guarantee that when any valid injected reference to a bean of normal scope is invoked, the invocation is always processed by the current instance of the injected bean. In certain scenarios, for example if a request scoped bean is injected into a session scoped bean, or into a servlet, this rule requires an indirect reference

    Also from this DZone CDI article:

    CDI handles the injection of beans with mismatched scopes through the use of proxies. Because of this you can inject a request scoped bean into a session scoped bean and the reference will still be valid on each request because for each request, the proxy re-connects to a live instance of the request scoped bean

    What this means is that, a proxy is substituted for the real thing at each injection point. The proxy mimics the type declared at the injection point by extending/implementing the ancestor tree of the type it's supposed to be mimicking. At the time you now actually require use of the object, the proxy performs a context-based lookup for an existing instance of the requested bean within the current conversation. This being a request-scoped object, you're guaranteed to have exactly one instance within the current conversation/context.

  2. why is the bean given @SessionScoped but not @Stateful. I guess @Stateful will work here.

    @Stateful would not work here, like I stated here, they are not cheap; unless you really need to, stick with vanilla HttpSession. Not to mention the fact that once the client of the SFSB releases the bean it's destroyed, i.e. the SFSB is not tied to the current session,@SessionScoped is.

  3. how is the lifecycle of @sessionScoped bean managed? That is when does it gets destroyed ?. If I navigate to a different JSF page in which if I pull the information such as currentUser.userName, will I retrieve the same information I set on my first JSF page used to log in. (step 1 above)

    Depends on which @SessionScoped you're referring to: javax.faces.bean.SessionScoped is tied directly to the current HttpSession/browser session, so it's terminated whenever that dies; JBoss however implies that javax.enterprise.context.* scoped beans don't actually go anywhere until the "context" dies

    There's actually no way to remove a bean from a context until the entire context is destroyed

  4. Think of @Dependent as you would any method-local variable: it's only useful as long as it's parent construct is around. That being said, it's best use is not for backing a JSF view. It's most useful application is overriding the scope that's specified on a bean, ad-hoc. Using your current example, I can have the following somewhere else in my application:

    @Inject @New Login aDependentLoginBean; //implicit @Dependent scope applied
    @Inject Login aSessionScopedLoginBean;  //standard Login bean's scope applied
    

    Together with @New, you could repurpose any other bean to be @Dependent


Related:

Clemmie answered 11/10, 2014 at 5:38 Comment(5)
I am reading this again and again. I have another post here.. #26285916Lempira
My comment on that question stands: your understanding of the situation is correct @brainstorm. Are you having trouble understanding this answer?Clemmie
you have indeed given a very detailed answer. Thank you very much for the same. I have a little problem in understanding the concept of proxies and how it works..please see edit above. I have added concrete questions on my understanding of your answer hereLempira
An explanation of CDI's proxying mechanism is way out of scope for this question @brainstorm. You'll need to ask a different question for an explanation of the proxy pattern and how proxying works in CDIClemmie
I agree proxying mechanism is a different question. I edited the part above to focus on my doubts. since your explanation was based on proxies, I initially extended my edit focussed on proxy. Now I removed it. ThanksLempira

© 2022 - 2024 — McMap. All rights reserved.