I'm building my first Java EE application using JSF, PrimeFaces, Glassfish and Netbeans. Because I'm new, it's possible I'm approaching the core problem wrong.
Core problem: I want to maintain user's information securely. There seems to be conflicting ideas on whether it should be maintained in a JSF session bean or a stateful session EJB. I'm trying to use a stateful session EJB because it is more secure that way.
The problem is that my application seems to be creating multiple instances of that bean when I expect it to create one and re-use it. If I refresh the page it runs the @PostConstruct
and @PostActivate
3 times, all of them with a different instances. Then they all get destroyed when I re-deploy the application.
Did I misunderstand how it should work or is something wrongly configured?
I'll try to show a trimmed down code sample:
basic.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"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
<c:if test="#{loginController.authenticated}">
Authenticated
</c:if>
<c:if test="#{loginController.authenticated}">
Authenticated
</c:if>
<c:if test="#{loginController.authenticated}">
Authenticated
</c:if>
</h:body>
</html>
LoginController
:
@Named(value = "loginController")
@RequestScoped
public class LoginController implements Serializable {
@EJB
private UserBeanLocal userBean;
public boolean isAuthenticated() {
return userBean.isAuthenticated();
}
}
UserBean
(excluding UserBeanLocal
interface)
@Stateful
public class UserBean implements UserBeanLocal, Serializable {
boolean authenticated = false;
@PostConstruct
@PostActivate
public void setup(){
System.out.println("##### Create user Bean: "+this.toString());
}
@Override
public boolean isAuthenticated() {
System.out.println("########## Authentication test is automatically passing.");
authenticated = true;//hard coded for simplicity.
return authenticated;
}
@PrePassivate
@PreDestroy
public void cleanup(){
System.out.println("##### Destroy user Bean");
}
}
Finally, here is the Glassfish output after refreshing three times:
INFO: ##### Create user Bean: boundary._UserBean_Serializable@2e644784
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ##### Create user Bean: boundary._UserBean_Serializable@691ae9e7
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ##### Create user Bean: boundary._UserBean_Serializable@391115ac
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
INFO: ########## Authentication test is automatically passing.
Stateful session beans (SFSB) are not exactly what you think they are. You seem to think that they behave somehow like session scoped JSF managed beans. This is untrue. The term "session" in EJBs has an entirely different meaning than the HTTP session which you've had in mind.
The "session" in EJBs must be interpreted in transactional context. The transaction (basically, the DB session) lives in case of SFSB as long as the client lives. The SFSB's client is in your particular example not the webbrowser, but the JSF managed bean instance itself, exactly the one where the SFSB is been injected. Since you have put the JSF managed bean in the request scope, the SFSB will be recreated on every HTTP request together with the JSF managed bean.
As an example, try to put the JSF managed bean in the view scope. The view scope is useful for a multi-step form on the same page, for example. Everytime when the view postbacks to itself, then the same JSF managed bean instance will be reused and this instance gives you access to the same instance of the SFSB as it is when the bean was created, which is not shared elsewhere. The SFSB transaction lives as long as the client (the view scoped JSF managed bean) lives.
A stateless session bean (SLSB) can be shared elsewhere, but that shouldn't matter as it's intented to be treated as stateless anyway. This "feature" saves the container time and memory to create and store them. The container can just have a pool of them. Even more, the SLSB instance which is been injected in a view, session or application scoped JSF managed bean does not necessarily need to refer exactly the same instance on every HTTP request as it was during JSF managed bean's creation. It can even be a completely different instance, depending on the available instances in the container's pool. The transaction lives (by default) as long as a single method call on the SLSB.
That said, a SFSB is unsuitable for your particular case of "remembering a logged-in user". That it's "more secure" makes really no sense. Just put the JSF managed bean in the session scope and let it remember the logged-in user by itself and make use of a SLSB to do any business actions (such as interacting with the DB) and use SFSB only when you want a real stateful session bean (I assume that you now understand what exactly they are :) ).