All of these approach worked, however they were not portable because they used JBoss annotations/configuration to do the trick.
Making an EJB as Singleton is pretty easy: just add the @java.ejb.Singleton annotation and that’s all.
Here’s an example:
@Singleton @Startup public class UserRegistry { public ArrayList<String> listUsers; @PostConstruct public void init() { listUsers = new ArrayList<String>(); listUsers.add("administrator"); } public void addUser(String username) { listUsers.add(username); } public void removeUser(String username) { listUsers.remove(username); } public ArrayList<String> getListUsers() { return listUsers; } }
In this example, the EJB exposes some methods to manage an in-memory cache based on an ArrayList.
Notice also the @Startup annotation which is not mandatory but can be used to signal to the container to invoke the @PostConstruct method just after the Bean has been created. So, if you need a proper Bean initialization, just add a @Startup annotation at Class level and a @PostConstruct at method level.
Testing the Singleton EJB is pretty simple, here’s a Servlet client:
@WebServlet("/servlet") public class ServletClient extends HttpServlet { @EJB UserRegistry ejb; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); ejb.addUser("frank"); out.println("List of users:") ArrayList<String> list = ejb.getListUsers(); for (String s:list) { out.println(s); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Please note that you won’t be able to look up remotely a Singleton EJB. If you need to access the Singleton EJB remotely you should include a wrapper Stateless Bean with a remote interface.
Managing concurrency in EJB singleton
Singleton session beans are designed for concurrent access, situations in which many clients need to access a single instance of a session bean at the same time.
There are two ways to manage concurrent access to Singleton EJBs. Let’s see them both.
Singleton Container-Managed Concurrency
If a singleton uses container-managed concurrency, the EJB container will manage access to the business methods of the singleton. This is the default if you don’t use any @ConcurrencyManagement annotation.
You can use the javax.ejb.Lock and javax.ejb.LockType annotation to specify the access level of the singleton’s business methods.
- Annotate a method with @Lock(LockType.READ) if the method can be concurrently accessed.
- Annotate a method with @Lock(LockType.WRITE) if the singleton session bean should be locked to other clients while a client is calling that method.
Typically, the @Lock(LockType.WRITE) annotation is used when clients are modifying the state of the singleton
The following example shows how to use the @ConcurrencyManagement, @Lock(LockType.READ), and @Lock(LockType.WRITE) annotations for a singleton that uses container-managed concurrency:
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) @Startup public class UserRegistry { public ArrayList<String> listUsers; @PostConstruct public void init() { listUsers = new ArrayList<String>(); listUsers.add("administrator"); } @Lock(LockType.WRITE) @AccessTimeout(value=60, timeUnit=SECONDS) public void addUser(String username) { listUsers.add(username); } @Lock(LockType.WRITE) @AccessTimeout(value=60, timeUnit=SECONDS) public void removeUser(String username) { listUsers.remove(username); } @Lock(LockType.READ) public ArrayList<String> getListUsers() { return listUsers; } }
You can specify how long the locks should be held by using the javax.ejb.AccessTimeout annotation.
The @AccessTimeout annotation can be applied to both @Lock(LockType.READ) and @Lock(LockType.WRITE) methods. .
The following excerpt shows how to apply a 1 minute timeout lock for some of your business methods:
@Lock(LockType.WRITE) @AccessTimeout(value=60, timeUnit=SECONDS) public void addUser(String username) { listUsers.add(username); } @Lock(LockType.WRITE) @AccessTimeout(value=60, timeUnit=SECONDS) public void removeUser(String username) { listUsers.remove(username); }
Singleton Bean-Managed Concurrency
Singletons that use bean-managed concurrency allow full concurrent access to all the business and timeout methods in the singleton. The developer is now responsible for ensuring that the state of the singleton is synchronized across all clients. Typically, if you want to code EJB singletons with bean-managed concurrency, you should use synchronization primitives, such as synchronization and volatile, to prevent errors during concurrent access.
Add a @ConcurrencyManagement annotation with the type set to ConcurrencyManagementType.BEAN at the class level of the singleton to specify bean-managed concurrency:
@ConcurrencyManagement(BEAN) @Singleton public class UserRegistry { ... }
Handling failures in a Singleton Session Bean
If an EJB singleton encounters an error when initialized by the EJB container, that singleton instance will be destroyed and you won’t be able to access it.
However, once it is initialized, a singleton EJB, it is not destroyed if the singleton’s business or lifecycle methods cause System exceptions. This ensures that the same singleton instance is used throughout the application lifecycle.
Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/ejb/ejb-singleton