devxlogo

EJB and RMI: A Practical Introduction

EJB and RMI: A Practical Introduction

ince their introduction in 1997, Remote Method Invocation (RMI) and Enterprise JavaBeans (EJB) have represented a new direction in the development, installation, and management of distributed Java applications in the enterprise. The surprisingly powerful RMI ushered Java developers into the world of distributed object transactions, while the componential architecture of EJB greatly simplified the development and management of corporate applications.

Today, Java developers frequently ask whether using EJB is even necessary when they can easily do the same things using only RMI?and vice versa in some cases. Certainly, you can use pure RMI to work with distributed objects in an architecture that also partially uses EJB, but whether you choose RMI or EJB depends almost completely on your needs and the required scalability of your application.

This article provides practical demonstrations for using RMI and EJB technologies, both individually and in tandem. It pays particular attention to the problems that arise when sharing EJB and RMI and discusses the scenarios where one is a better choice than the other. All the working methods include simple examples and diagrams that illustrate the working principles behind these technologies.

RMI Architecture
The basic objective of RMI is to allow programmers who develop distributed Java applications to use the same syntax and semantics that they use for non-distributed applications. To do this, the creators had to carefully investigate how Java classes and objects function on separate Java Virtual Machines (JVMs) and invent a new model for working with classes and objects accordingly. The new model would have to accommodate environments in which distributed object transactions occur (e.g., on several JVMs).

The architecture of RMI defines:

  • How objects are conducted;
  • How memory management works, in which place;
  • Why exceptions exist; and
  • Which parameters (arguments) are sent into remote methods, and how they return.

RMI technology architecture is based on one principle: the definition of behavior and the implementation of this behavior should be kept separate. RMI allows developers to separately store the behavior description and its implementation code on several, separately working JVMs. This concept is ideal for distributed systems where the client needs to know only the definition of a service used and the server directly provides this service (its implementation).

Initially, RMI uses Java interfaces as the definition of the remote service. Accordingly, the implementation of the service is coded in Java classes. So a key to understanding RMI is realizing that interfaces define behavior, while classes define implementation. (Figure 1 shows this concept of division.)

Click to enlarge
Figure 1: RMI Concept of Division

Remember that interfaces do not contain any executable code. RMI supports two classes that implement these interfaces. The first class directly implements behavior and works on the server side. The second plays the role of proxy for the remote service and works on the client side.

When the client program sends a call of a proxy object method, RMI sends the request to a remote JVM, which sends this request to the implementation code. Any returned values of a called method are sent back to the proxy object and then to the client.

RMI Implementation
RMI implementation consists of three abstract layers:

  1. Stub and Skeleton classes, which hide implementation from the developer. This layer intercepts the set interface’s methods calls (which the client sends) and redirects them to their remote RMI service.
  2. Remote reference layer. This layer knows how to interpret and operate the references from the client to the remote service’s objects.
  3. Transport layer. This layer works with TCP/IP connections between networked machines. It enables the creation of connections and provides some detour strategies for firewall systems.

This architecture allows developers to replace any of these three layers with a new layer that contains different behavior logic. For example, you can replace a transport layer with a layer that supports UDP/IP protocols. As a result, none of the top layers will be visible.

You may be asking, how does the client define a location for the RMI service (server)? Special name services and directories, including Java Naming and Directory Interface (JNDI), are used for this purpose. But RMI includes a similar, rather simple service called RMI Registry (rmiregistry). RMI Registry must be started on each machine that acts as a storehouse of objects of the remote service (i.e., on a server) and accepts requests/connections to a specified port (the default port is 1099).

On the server side, before creating a remote service, the program first creates a local object, which is an implementation of this service. It then exports this object in RMI, and RMI creates a service that waits for the client’s connections and requests. After export, the server registers this object in RMI Registry under a certain public name.

On the client side, the program addresses the RMI Registry with the help of the Naming static class. The program uses the lookup() method of this class to receive a URL string, which defines the names of a server and a desired service. This method returns the remote reference to the service object. The URL usually looks like this:

rmi://[:]/ 

HOST_NAME is the host name, PORT tells the port number (e.g., 1099 by default) and is not necessarily an argument, and SERVICE_NAME is the name of the service.

RMI in Use
The arithmetic calculator is a simple example of the distributed application, which books and articles very frequently use. It has almost become the traditional example, and who are we to break tradition? To present an RMI system, this demonstration will carry out the actual calculations on the server side and receive the results on the client side.

A working RMI system consists of the following parts:

  • Remote services interface definitions
  • Remotes services implementation
  • Stub and Skeleton files
  • A server with remote services
  • RMI Naming service, which allows clients to locate remote services
  • Container (“supplier”) of Java classes (HTTP or FTP server)
  • A client program, which requires remote services

Definition of Interfaces
For the calculator interface, the remote service offers the following methods:

public interface Calculator extends java.rmi.Remote {     public long add(long a, long b) throws java.rmi.RemoteException;     public long sub(long a, long b) throws java.rmi.RemoteException;     public long mul(long a, long b) throws java.rmi.RemoteException;     public long div(long a, long b) throws java.rmi.RemoteException; } 

Each of the listed methods can throw out an exception of RemoteException class or its derivatives. Each of these methods carries out a corresponding arithmetic operation (addition, subtraction, multiplication, and division) between two numbers, a and b.

Save all the methods above as Calculator.java and compile them with the following command:

C:j2sdk1.4.1_02injavac Calculator.java

The Java SDK is placed in the default directory C:j2sdk1.4.1_02, but if you have it in another location, specify your own path to the Java compiler.

Implement this interface for your server as well. Name the implementation class CalculatorImpl.java:

public class CalculatorImpl extends java.rmi.server.UnicastRemoteObject implements Calculator {      // Implementation need to have constructor to define possibility of    // RemoteException    public CalculatorImpl() throws java.rmi.RemoteException {         super();     }     public long add(long a, long b) throws java.rmi.RemoteException {         return a + b;     }     public long sub(long a, long b) throws java.rmi.RemoteException {         return a  b;     }     public long mul(long a, long b) throws java.rmi.RemoteException {         return a * b;     }     public long div(long a, long b) throws java.rmi.RemoteException {         return a / b;     } } 

Each of the methods offered in the implementation performs an arithmetic function.

As you can see, CalculatorImpl.java also extends the UnicastRemoteObject class, directly entering it into the RMI system. You don’t have to extend the class to enter it into the RMI system, however. You can take advantage of the exportObject() method instead.

Since the CalculatorImpl class extends UnicastRemoteObject, you also have to define the constructor, designating from which declare it can throw out the RemoteException exception. When the constructor invokes the method super(), it carries out the constructor UnicastRemoteObject code, which communicates with RMI and initializes the remote objects.

The next step is to create the files Stub and Skeleton. You can do this with a special RMI compiler, which enables you to set the name of a class as an argument with the implementation. For example, enter the following command:

rmic CalculatorImpl

Finally, you need to write the server and client parts for the application. In this case, the following is the server-side code for your calculator:

import java.rmi.Naming;public class CalculatorServer {   public CalculatorServer() {     try {       Calculator c = new CalculatorImpl();       Naming.rebind("rmi://localhost:1099/CalculatorService", c);     } catch (Exception e) {       System.out.println(e.printStackTrace());     }   }   public static void main(String args[]) {     new CalculatorServer();   }}

As you can see, this class simply creates a copy of the Calculator interface based on its implementation (CalculatorImpl) and assigns it a name in the naming service (with the help of the Naming.rebind() method).

So the client-side code will look like this:

import java.rmi.Naming; import java.rmi.RemoteException; import java.net.MalformedURLException; import java.rmi.NotBoundException;  public class CalculatorClient {     public static void main(String[] args) {         try {             Calculator c = (Calculator) Naming.lookup("rmi://localhost/CalculatorService");             System.out.println( c.sub(8, 2) );             System.out.println( c.add(4, 5) );             System.out.println( c.mul(6, 7) );             System.out.println( c.div(8, 2) );         }         catch (Exception e) {             System.out.println(e.printStackTrace());         }     }} 

Execute Your RMI System
Now, having compiled all the classes and created the files Stub and Skeleton, you can execute your RMI system, which requires three separate consoles. In the first one, you start RMI Registry with the command rmiregistry. In the next console, start your server with the command java CalculatorServer. The server will start, load the implementation into memory, and wait for connections from clients. Lastly, start the client application with the command java CalculatorClient. If everything passes correctly, you will see the following result in the client console:

69424

That’s it. You have created and tested your own complete RMI system.

EJB Architecture
Enterprise JavaBeans defines the server componential model and the interface for server-vendor-independent programming of Java applications. EJB initially utilized two fundamental models for constructing enterprise applications: the session bean and the entity bean. In the first model (session bean), the client begins a session with an object, which behaves like the application, carrying out units of work on behalf of the client with the ability to perform multiple database transactions. In the second model (entity bean), the client gets access to an object stored in a database, which represents a certain entity.

Figure 2 presents the general architecture of EJB technology.

Click to enlarge
Figure 2: General Architecture of EJB Technology

EJB architecture allows those working with an application server and its components to use absolutely any client. The absence of a precisely fixed, specific protocol makes this possible. The server can support various communication protocols with the client (e.g., RMI, IIOP (CORBA), and DCOM), simultaneously. Therefore, the client does not have to be written in Java.

An EJB server provides a set of services for EJB support. These services include management functions for distributed transactions, distributed objects, and remote object calls, along with low-level system services. More to the point, the EJB server offers all the necessary resources for supporting EJB components.

Using EJB Under RMI
A number of scripts are available for the construction of EJB applications and components. EJB development differs depending on what you’re creating, whether a session bean component, an entity bean, an integral application, or the whole system, which includes a few of these types at once.

Consider the simple algorithm for developing a session bean component that updates a special account. Developing an EJB component is not, in and of itself, complex. First, you describe the business logic of your component or application using an IDE (e.g., Eclipse or NetBeans). After compilation, pack the components in an EJB JAR file (a JAR archive that contains a serialized copy of the DeploymentDescriptor class). The file contains adjustments for security and various descriptions. Furthermore, you must deploy this component (session bean) into an EJB server with server-specific tools that the vendor bundles with the server. After the deployer (for example, a database administrator) adjusts various specific attributes of this component (e.g., a mode of transactions or a level of security). As soon as the component is established on a server, clients can call remote methods of the received objects.

To demonstrate using EJB under RMI, this section uses an example from the field of electronic commerce: shopping cart. Shopping cart is an abstract container into which the e-shopper puts the goods he or she is going to purchase.

Consider an example of shopping cart as an EJB component. First, you create the remote interface for your component:

public interface ShoppingCart extends javax.ejb.EJBObject {    boolean addItem(int itemNumber) throws java.rmi.RemoteException;    boolean purchase() throws java.rmi.RemoteException;}

This interface defines two methods: addItem() and purchase(). The first adds goods to shopping cart, and the second completes the transaction. After that, you must write a class of your component:

public class ShoppingCartEJB implements SessionBean {  public boolean addItem(int itemNumber) {    // process of adding any goods into shopping cart    // here can be database interaction with the help of JDBC  }  public boolean purchase () {    // code to proceed with purchasing  }  public ejbCreate(String accountName, String account) {    // object initializing  }}

Notice this class does not implement the remote interface of the component described previously. The EJBObject class will do that later. Also note that components such as session bean do not support automatic persistence mode. Therefore, direct access to a database should be made in the session bean’s methods. For example, in the method purchase() you can use JDBC calls. You can also use them for updating the information in a database (if needed).

The EJBObject class, which a server’s EJB container creates during component installation, implements the remote interface. This class operates as a proxy, passing method calls through itself and transferring them to a component (which is deployed on the server).

On the client side, the client should first use the JNDI service to locate the EJBHome object of the required component. In the shopping cart example, you do this with the following code:

public interface CartHome extends javax.ejb.EJBHome {	Cart create(String customerName, String account) throws RemoteException;}

Interface CartHome contains a method create(), which the client will call each time it requests a new copy of your component. This method is already implemented in the EJBObject class, so by calling it, the client also calls the ejbCreate() method of your component’s class.

Now consider an example of how the code on the client side, which uses a session bean for shopping cart, can look. The client can be a “thin client” (browser) or an application written either in Java or C++ (using CORBA technology). But this article doesn’t consider the latter because its covers EJB under RMI, not IIOP.

You can get the EJBHome object for the class ShoppingCart by using the following piece of code:

Context initialContext = new InitialContext();CartHome cartHome = (CartHome) initialContext.lookup("applications/mall/shopping carts");

In this snippet, InitialContext() gets the root of all JNDI names in its hierarchy, lookup() gets the CartHome object, and “applications/mall/shopping carts” is a JNDI path to the required CartHome class. Thus, now you have the cartHome reference to the EJBHome object for getting access to ShoppingCartEJB. However, don’t forget that the JNDI naming space can be configured to include EJB containers located on different computers in a network. The client cannot determine the actual arrangement of the EJB container.

The following code shows how the client uses your component’s EJBHome object call methods:

ShoppingCartEJB cart = cartHome.create("green","333"); cart.addItem(162);cart.addItem(375);cart.purchase();

Here, you used the create() method to create a new object of the session bean component. The variable cart now contains the reference to a remote EJB object and allows you to call its methods: addItem() and purchase().

Pure RMI or EJB?
Why use EJB when RMI enables you to easily do the same things without it? The answer depends on many factors, but perhaps a better question is ‘why not use pure RMI to work with distributed objects even if your architecture partially uses EJB?’ In fact, the example application in the previous section would work much faster this way, since you described practically everything independently.

The EJB framework is sufficient as a general framework that you can use in most cases where distributed components are necessary. And with such general use, its code should carry out many checks and have enough logic layers to solve practically any type of problem that comes up. All this results in significant productivity loss, however. Therefore, if the top priority of the application is productivity, the benefit of pure RMI code makes it the logical choice. Besides, you can always bypass the RMI framework altogether and work directly with the Sockets API. But again, understand that whether you choose RMI or EJB depends almost completely on your needs and the required scalability of your application.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist