Search code examples
sessionejbstateful

Issue with stateful EJB - keeps one session for all users


I am quite new to EJB. I need stateful bean in my EAR application. I have created simple stateful session bean in an ejb module:

@Stateful
public class Test {

    public Test() {
    }

    private int i;

    @PostConstruct
    public void initialize() {
        i = 0;
    }

    public int getI() {
        return i++;
    }
}

And I call it from servlet in a war module:

public class TestServlet extends HttpServlet {

    @EJB
    Test test;

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        HttpSession session = request.getSession(true);

        out.println("<html>");
        out.println("<head>");
        out.println("<title></title>");            
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>" + test.getI() + "</h1>");
        out.println("</body>");
        out.println("</html>");         
        out.close();
    }
...
}

When I run it, the number gets bigger with each refresh of a browser.

0, 1, 2, ...

But when I run it in another browser, the number doesn't start from 0 but continues the session from the previous browser. It behaves like singleton.

3, 4, 5, ...

Why wasn't a new instance of the bean created? I tried to put the session bean into the war module, or annotate it with SessionScoped but result is the same.

Can you help me to create a new instance of the stateful bean for each http session?


Solution

  • A single servlet is created to service all requests. You're injecting an instance of a single stateful session bean into the servlet, so all requests will use that same stateful session bean. It is rarely useful to inject stateful session beans.

    You need to create a per-HttpSession instance of the stateful session bean:

    // Declare the reference without injection...
    @EJB(name="ejb/test" beanInterface=Test.class)
    public class TestServlet extends HttpServlet {
        protected void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
    
            HttpSession session = request.getSession(true);
            Test bean = session.getAttribute(BEAN);
            if (bean == null) {
                try {
                    // ...and then create an instance by looking it up from java:comp/env
                    bean = (Test)new InitialContext().lookup("java:comp/env/ejb/test");
                } catch (NamingException ex) { ... }
                session.setAttribute(BEAN, bean);
            }
    
            ...
        }
    }
    

    Alternatively, if you have CDI, you could mark your stateful session bean as @SessionScoped and use @Inject for injection rather than @EJB.