Introduction
With Java EE you can annotate simple java POJO classes with @Stateless and @Singleton markers and they become full fledged EJB beans with middleware services like transactions and security.
But on the contrary to the CDI bean scopes like @RequestScoped or @ApplicationScoped it is not immediate clear how long the EJB bean lives.
Of course, the term Singleton indicates that there is just 1 bean created, but how is then concurrency handled.
This is the first part of a two part series about EJB beans.
Warning, in this text I use a Stateless EJB bean to store some state, here a counter which is not good practice as you also see throughout the text. It is used to better indicate and explain what is going on.
Code
The code I use through out this text is quite simple. I keep a counter instance variable and have a method to increment this counter and return the current value.
@Stateless public class SomeEJB { private long counter = 0; public long incrementAndGetCounter() { counter++; return counter; } ...
}
For the thread safety aspects, I use an asynchronous EJB method call where I put in a wait of 2 seconds. Due to the Future return type of the method, the caller of this method gets the control back before the method has finished.
@Asynchronous public Future<Long> asyncIncAndGet() { counter++; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return new AsyncResult<Long>(counter); }
This code will be used in three EJB beans (Singletons beans will be explained in a next text item). For this text we use the @Statless annotation as shown in the above code example.
The ‘client’ of the EJB beans is a CDI bean with scope @Model. This means that each request from the browser we get a new instance and thus a newly injected EJB bean.
In the browser we see the output of our test and how long it took through the use of an JSF page.
@Model public class DemoBean { @EJB private SingletonEJB someEJB; private String data; private long elapsedTime; public void testEJB() { long start = System.currentTimeMillis(); scenario1(); // or 2 or 3 elapsedTime = System.currentTimeMillis() - start; } ...
}
Servers
I use GlassFish 4 and WildFly 8 to verify the results of the tests. And as you will see, both servers has a different approach but both are compliant with the specifications.
Scope of @Stateless
Lets start by calling the scenario 1 method on the Glassfish server. If we click the button on the JSF page, we get the following result (timing is omitted here as only relevant for multithreaded scenarios)
public void scenario1(){ data = String.valueOf(someEJB.incrementAndGetCounter()); }
click 1 -> value = 1
click 2 -> value = 2
click 3 -> value = 3
Each click increases the value of the counter and since the value keeps incrementing, it is clear that we get the same EJB injected in the CDI bean each time we click on the button.
If we run scenario 2 on GlassFish, we get results that follow the same pattern.
public void scenario2() { data = ""; for (int i = 0; i < 5; i++) { data += " - " + String.valueOf(someEJB.incrementAndGetCounter()); } }
click 1 -> value = 1 - 2 - 3 - 4 - 5
click 2 -> value = 6 - 7 - 8 - 9 - 10
click 3 -> value = 11 - 12 - 13 - 14 - 15
So is @Stateless behaving as a @Singleton? Lets investigate more.
If we run the same scenarios on WildFly server, we get different result and some of them may surprise you.
click 1 -> value = 1
click 2 -> value = 1
click 3 -> value = 1
Now it is clear that we receive each time a new instance of the EJB bean. And thus the value we get back is 1 each time.
Is this correct? Yes. As the names says, it is a stateless session bean and thus we shouldn’t make any assumptions about which instance of the bean we receive the following invocation time.
This are the results if we run scenario 2 on WildFly
click 1 -> value = 1 - 1 - 1 - 1 - 1
click 2 -> value = 1 - 1 - 1 - 1 - 1
click 3 -> value = 1 - 1 - 1 - 1 - 1
And this is a surprise, no? We execute the method 5 times on the same injected EJB method. So you could think that it is 5 times the same bean.
But in fact, we get a proxy injected into the CDI bean, not the actual EJB bean (check the getClass().getName() outcome). This is because there are possibly additional ‘interceptors’ executed for instance to handle the transactional aspects of database access.
So WildFly decided that the proxy is allowed to access another EJB bean instance each time you ask for a method call.
And after clicking a lot of times on the JSF button, I even have the impression that you receive each time a new instance and that there is no reuse ( or the pool of EJB beans must be very very large)
Exact lifetime of EJB beans on GlassFish
What happens if we concurrently access a stateless EJB bean on GlassFish? This is scenario 3. Let me first explain a bit what it does.
public void scenario3() { data = ""; List<Future<Long>> results = new ArrayList<>(); for (int i = 0; i < 5; i++) { results.add(someEJB.asyncIncAndGet()); } for (Future<Long> result : results) { try { data = data + " - " + String.valueOf(result.get(3, TimeUnit.SECONDS)); } catch (Exception e) { e.printStackTrace(); } } }
The EJB method is asynchronous and returns immediate an object where we can retrieve the result of the method execution in some near future. So our client is able to call this asynchronous method, and immediately afterwards, calling that same method again (5 times in total in our example).
After we have called it enough times, we wait for the result to return with the result.get().
And what do you think the result will be, the value 5, 5 times in a row? Not exactly, again a surprise isn’t it?
click -> value : 1 - 1 - 1 - 1 - 1
elapsed time : 2008
The second call to the asynchronous method takes place when the first execution is still ‘active’. Again, the proxy which is injected, decides to call another EJB instance.
Use of instance variables
So how should we use Stateless session EJB beans? The specification states that only one client at a time has access to the EJB bean instance. If another clients needs to access some functionality of the bean, it gets access to another instance.
But once the action is performed, the bean can be returned to the pool and used by another client (or same client the next time).
So it is safe to use instance variables to transfer information between private method calls. But we shouldn't store information instance variables that we expect to linger around for a next call to the EJB bean.
Conclusion
When we inject an EJB bean, we get a proxy to this bean. And this proxy bean will select a free instance of our EJB bean from the pool. Of course, when there is no bean available, it will create a new instance.
In GlassFish, the EJB bean, will return to the pool when it is no longer accessed by the client and can be used during some next call by the same or any other client.
In WildFly, the EJB bean is always destroyed and thus never reused.
It is safe to store information into instance variables for the duration of one call be it is wrong to use it to keep information between different calls.
Next time we discuss the @Singleton EJB bean.