A Detailed Guide to EJBs With Code Examples
EJBs can be a little confusing if you don't know the ins and outs with a certain degree of clarity. Let's take a closer look at them with the help of some examples.
Join the DZone community and get the full member experience.
Join For FreeBy 1996, Java had already become popular among developer for its friendly APIs and automated Garbage Collection and was starting to be widely used in back-end systems. One problem, however, was that most of these systems needed the same set of standard capabilities — such as persistence, transaction integrity, and concurrency control — which the JDK lacked at that time. That, naturally, led to many home-grown, closed implementations.
IBM stepped forward and released the Enterprise Java Bean (EJB) specification in 1997, with the promise that developers could write code in a standard way, with many of the common concerns automatically handled.
That's how the first Java framework for the enterprise was born; the specification was later adopted by Sun in 1999 as EJB 1.0.
Fast forward twenty years and EJB 3.2 is now the subset of the JavaEE 9 specification.
What Is an Enterprise Java Bean
Simply put, an Enterprise Java Bean is a Java class with one or more annotations from the EJB spec which grant the class special powers when running inside of an EJB container. In the following sections, we'll discuss what these powers are and how to leverage them in your programs.
A side note - annotations in EJB are relatively new and are available since EJB 3.0. Previous versions of EJB used to have interfaces which classes had to implement. I'm not going to cover that in this article.
JNDI Names
JNDI or Java Naming Directory Interface is a directory service which allows lookup of resources. Every resource like an EJB, a Datasource or a JMS Queue running on an application server is given a JNDI name which will be used to locate the resource.
All servers have a default scheme of assigning JNDI names but it can be overridden to provide custom names. The general convention is {resourceType}/{resourceName}. For example, a DataSource's JNDI name can be jdbc/TestDatabase and a JMS queue can have jms/TestQueue as JNDI name.
Types of Enterprise Beans
Let's now go a bit deeper into the specifics of Enterprise beans:
- Session Beans
- Message-Driven Beans
Session Beans
A session bean encapsulates business logic that can be invoked programmatically by a client. The invocation can be done locally by another class in the same JVM or remotely over the network from another JVM. The bean performs the task for the client, abstracting its complexity similar to a web service, for example.
The lifecycle of a session bean instance is, naturally, managed by the EJB container. Depending on how they're managed, sessions beans can be in either of the following states:
As the name suggests, Stateless beans don't have any state. As such, they are shared by multiple clients. They can be singletons but in most implementations, containers create an instance pool of stateless EJB. And, since there is no state to maintain, they're fast and easily managed by the container.
As a downside, owing to the shared nature of the bean, developers are responsible to ensure that they are thread safe.
Stateful beans are unique to each client, they represent a client's state. Because the client interacts ("talks") with its bean, this state is often called the conversational state. Just like stateless beans, instance lifecycle is managed by the container; they're also destroyed when the client terminates.
A Singleton session bean is instantiated once per application and exists for the lifecycle of the application. Singleton session beans are designed for circumstances in which state must be shared across all clients. Similar to Stateless beans, developers must ensure that singletons thread safe. However, concurrency control is different between these different types of beans, as we'll discuss further on.
Now, let's get practical and write some code. Here, we're going to create a Maven project with a packaging type of ejb, with a dependency on javaee-api:
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackify</groupId>
<artifactId>ejb-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ejb</packaging>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
</dependency>
</dependencies>
</project>
Alternatively, we could include the target server runtime dependency instead of the JavaEE APIs, but that does reduce portability between different containers.
Modern-day EJB is easy to configure, hence writing an EJB class is just a matter of adding annotations i.e. , or . These annotations come from the javax.ejb package:
@Stateless
public class TestStatelessEjb {
public String sayHello(String name) {
return "Hello, " + name + "!";
}
}
Or:
@Stateful
public class TestStatefulEjb {
}
Finally:
@Singleton
public class TestSingletonEjb {
}
There's also a javax.inject.Singleton annotation, but that's a part of the CDI spec, so we need to be aware of that if we're going to use it.
Message-Driven Beans
A message-driven bean or MDB is an enterprise bean that allows you to process messages asynchronously. This type of bean normally acts as a JMS message listener, which is similar to an event listener but receives JMS messages instead of events.
They are in many ways similar to a Stateless session bean but they are not invoked by a client. instead, they are event-driven:
@MessageDriven(mappedName = "jms/TestQueue")
public class TestMessageDrivenBean implements MessageListener {
@Resource
MessageDrivenContext messageDrivenContext;
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
TextMessage msg = (TextMessage) message;
msg.getText();
}
} catch (JMSException e) {
messageDrivenContext.setRollbackOnly();
}
}
}
Here, the mapped name is the JNDI name of the JMS queue that this MDB is listening to. When a message arrives, the container calls the message-driven bean's onMessage method to process the message. The onMessage method normally casts the message to one of the five JMS message types and handles it in accordance with the application's business logic. The onMessage method can call helper methods or can invoke a session bean to process the information in the message.
A message can be delivered to a message-driven bean within a transaction context, so all operations within the onMessage method are part of a single transaction. If message processing is rolled back, the message will be redelivered.
Accessing Enterprise Beans
As discussed before, MDBs are event-driven, so in this section, we'll talk about how to access and invoke methods of session beans.
To invoke the methods of an EJB locally, the bean can be injected in any managed class running in the container — say a Servlet:
public class TestServlet extends HttpServlet {
@EJB
TestStatelessEjb testStatelessEjb;
public void doGet(HttpServletRequest request,
HttpServletResponse response) {
testStatelessEjb.sayHello("Stackify Reader");
}
}
Invoking the method from a remote JVM is trickier and requires a bit more code. As a prerequisite, EJB must implement a remote interface to enable remoting capabilities. You will need to write an EJB client which will perform a lookup over the network.
The interface is annotated with :
@Remote
public interface TestStatelessEjbRemote {
String sayHello(String name);
}
Make sure that the TestStatelessEjb implements this interface.
Now let's write the client which in this case would just be a simple Java SE application with the main method:
public class TestEjbClient {
public static void main(String[] args) throws NamingException {
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
properties.setProperty(Context.PROVIDER_URL, "ejbd://host:4201");
Context context = new InitialContext(properties);
TestStatelessEjbRemote testStatelessEjbRemote
= (TestStatelessEjbRemote) context.lookup("ejb/TestStatelessEjbRemote");
testStatelessEjbRemote.sayHello("Stackify");
}
}
First, we created a Context with properties referring to the remote JVM. The initial context factory name and the provider URL used here are defaults for Open EJB and will vary from server to server.
Then we performed a lookup of the EJB by using the JNDI name of the bean and then typecast it to the desired remote type. Once we get the remote EJB instance, we were able to invoke the method.
Note that you'll need two JAR files in the classpath of your client:
- One containing the initial context factory class. This will vary from server to server.
- Another containing the remote interface of your EJB.
As it happens, the Maven EJB plugin will generate a client jar file which will only have all the remote interfaces. You just need to configure the plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<!-- this is false by default -->
<generateClient>true</generateClient>
</configuration>
</plugin>
In case of Stateful beans, a new instance of the bean is returned every time a client performs a lookup. In case of Stateless beans, any one bean from the pool is returned.
Concurrency in Singleton Beans
With both Stateless and Stateful enterprise beans, methods can be concurrently invoked by multiple clients or by multiple threads from the same client. However, in case of Singleton enterprise beans, the default mode is LockType.WRITE. This means that only one thread is allowed to invoke the method at once.
That can be changed by adding the annotation over a method and setting to LockType.READ:
@Singleton
public class TestSingletonEjb {
@Lock(LockType.READ)
public String sayHello(String name) {
return "Hello, " + name + "!";
}
}
This fine-grained concurrency management over method level allows developers to build robust multi-threaded applications without having to deal with actual threads.
Say we have a Map instance variable in a Singleton EJB. Most clients read from the Map but a few do put elements into it. Marking the get method as lock type read and put method as lock type write would make up for a perfect implementation:
@Singleton
public class TestSingletonEjb {
private Map<String, String> elements;
public TestSingletonEjb() {
this.elements = new HashMap<>();
}
@Lock(LockType.READ)
public String getElement(String key) {
return elements.get(key);
}
@Lock(LockType.WRITE)
public void addElement(String key, String value) {
elements.put(key, value);
}
}
A write-lock locks the whole class, so when the map is being updated in the addElement method, all the threads trying to access getElement will also be blocked.
EJB Timers
Running scheduled jobs in EJB is simplified to the maximum possible level i.e. adding the annotation over the method that needs to be invoked. Parameters of this annotation configure when the timer will be executed:
@Singleton
public class TestScheduleBean {
@Schedule(hour = "23", minute = "55")
void scheduleMe() {
}
}
Note here that the EJB is a Singelton. This is important because only singleton beans guarantee that only one instance of the bean will be created and we don't want our scheduler to be fired from multiple instances.
Conclusion
Although Spring has gained a lot of traction in the enterprise development world, EJB is still very relevant and quite powerful. Out of the box remoting capabilities and concurrency management are still exclusive to Enterprise Beans; JMS and JPA are a part of the JavaEE spec as well and hence treated as first-class citizens in EJB.
EJB has certainly evolved beyond its previous limitations and has re-invented itself into a modern and powerful tool in the rich Java ecosystem.
Published at DZone with permission of Eugen Paraschiv, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments