EJB passivation and activation example
Introduction
Stateful Session Beans are usually coupled with some given client and - as the name states - they keep state across client invocations.
Since a conversation with a client may be long-lived, and many simultaneous clients may exist at the same time, the EJB container must have a mechanism of releasing resources that may not be needed at some precise moment.
This is where EJB passivation and activation comes into: The container may passivate an EJB that is not needed at some point in time and later activate it (or recover it) as soon as it's needed again.
This tutorial considers the following environment:
- Ubuntu 12.04
- JDK 1.7.0.21
- Glassfish 4.0
The Stateful Session Bean
We start this tutorial by defining the Stateful Session Bean:
package com.byteslounge.ejb; import javax.ejb.PostActivate; import javax.ejb.PrePassivate; import javax.ejb.Stateful; import com.byteslounge.model.TestingObject; @Stateful public class TestingPassivationBean implements TestingPassivation { private TestingObject testingObject; @Override public void setTestingObject(TestingObject testingObject) { this.testingObject = testingObject; } @Override public TestingObject getTestingObject() { return testingObject; } @PrePassivate private void prePassivate(){ // Free resources (ex: JDBC connections) // ... System.out.println("Passivating EJB. Property value: " + testingObject.getTestingProperty()); } @PostActivate private void postActivate(){ // Reinitialize resources (ex: JDBC connections) // ... System.out.println("Activating EJB. Property value: " + testingObject.getTestingProperty()); } }
The EJB itself is kept as simple as possible so we may focus in the relevant tutorial details. Method prePassivate is annotated with @PrePassivate. This annotation instructs the EJB container to call this method before passivating the EJB.
Method postActivate is annotated with @PostActivate. This annotation instructs the EJB container to call this method after activating - or recovering - the EJB.
EJB passivation should be used to release any resources the EJB may be keeping like JDBC connections, references to files, JMS listeners, among others. During EJB activation the resources should be reinitialized if needed.
EJB properties - in this case an instance of type TestingObject - will also be passivated and recovered during bean passivation/activation by default. Keep in mind that EJB passivation is actually implemented with Java serialization so all EJB properties must be serializable (or transient if we don't want them to be kept after passivation/activation). We will cover the TestingObject type in the next section.
Finally the EJB is implementing an interface that is a simple Local view of the EJB:
package com.byteslounge.ejb; import javax.ejb.Local; import com.byteslounge.model.TestingObject; @Local public interface TestingPassivation { void setTestingObject(TestingObject testingObject); TestingObject getTestingObject(); }
The TestingObject type
As we have seen in the previous section our testing EJB has a property of type TestingObject. This class is defined as:
package com.byteslounge.model; import java.io.Serializable; public class TestingObject implements Serializable { private static final long serialVersionUID = 1L; private String testingProperty; public TestingObject(String testingProperty){ this.testingProperty = testingProperty; } public String getTestingProperty() { return testingProperty; } }
This is the definition of the EJB testingObject property and it will be used to illustrate that the serializable EJB properties are kept after EJB passivation and activation. Once again if you don't want your EJB properties to be kept after EJB passivation and activation you should declare them as transient.
Testing
Now that we have our EJB ready to be tested let's do some tuning of the EJB container in order to force EJB passivation to occur as soon as possible.
Since we are using Glassfish as the EJB container, there is a configuration file that may be used to change it's default settings:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd"> <sun-ejb-jar> <enterprise-beans> <ejb> <ejb-name>TestingPassivationBean</ejb-name> <bean-cache> <max-cache-size>5</max-cache-size> <cache-idle-timeout-in-seconds>10</cache-idle-timeout-in-seconds> </bean-cache> </ejb> </enterprise-beans> </sun-ejb-jar>
The sun-ejb-jar.xml is a configuration file that may be used along with Glassfish in order to change the EJB container default settings. We are setting both the EJB maximum cache size and idle timeout to very low settings so it becomes easy to force the container to start passivating EJBs.
Now let's define a testing Servlet to create multiple EJB instances:
package com.byteslounge.servlet; import java.io.IOException; import javax.naming.InitialContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.byteslounge.ejb.TestingPassivation; import com.byteslounge.model.TestingObject; @WebServlet(name = "testingServlet", urlPatterns = {"/testing"}) public class TestingServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InitialContext ic; TestingPassivation testingPassivation; String beanCountParam = request.getParameter("count"); if(beanCountParam != null){ int beanCount = Integer.parseInt(beanCountParam); try{ ic = new InitialContext(); for(int i = 0; i < beanCount; i++){ testingPassivation = (TestingPassivation) ic.lookup("java:global/com-byteslounge/com-bytesloungeejb/TestingPassivationBean"); testingPassivation.setTestingObject(new TestingObject("bean" + i)); request.getSession().setAttribute("bean" + i, testingPassivation); } } catch(Exception e){ throw new ServletException(e); } } String beanIndex = request.getParameter("activate"); if(beanIndex != null){ try{ ic = new InitialContext(); testingPassivation = (TestingPassivation) request.getSession().getAttribute("bean" + beanIndex); System.out.println("TestingObject property value: " + testingPassivation.getTestingObject().getTestingProperty()); } catch(Exception e){ throw new ServletException(e); } } } }
This Servlet accomplishes a couple of distinct tasks:
The first is creating EJBs if the parameter count is present in the request parameters. It will create as man EJBs as specified in the parameter and store all EJB instances in the HTTP session.
The second is to fetch an EJB from the HTTP session if the parameter activate is present in the request parameters. It will fetch the instance which index is specified in the parameter.
After deploying the application in the container when we request the following URL:
http://localhost:8080/byteslounge/testing?count=100
The container will create 100 instances of our EJB. Since we configured the EJB container cache and timeout to very low settings we will see very soon in the server logs evidence of EJB passivation:
Passivating EJB. Property value: bean12
...
Passivating EJB. Property value: bean11
Now if we request the following URL:
http://localhost:8080/byteslounge/testing?activate=12
The EJB which property value was bean12 was previously passivated as we have seen in the logs. As soon as we invoke an EJB method it will be activated and the following will be visible in the server logs:
TestingObject property value: bean12
Troubleshooting
If you are also running Glassfish and notice the following error message in your server logs:
Make sure that all your EJB properties hierarchy are implementing the Serializable interface (or declare them as transient if you don't need the properties to be kept after bean passivation and serialization).
Downloadable sample
The downloadable code sample at the end of this page contains the full source code used in this example.