Appendix A. RMI and JNDI – EJB 3 in Action

Appendix A. RMI and JNDI

Java Remote Method Invocation (RMI) and the Java Naming and Directory Interface (JNDI) are two central Java technologies that EJB 3 uses extensively under the hood. RMI is the technology that enables transparent Java native remote communication between EJB clients and beans. JNDI, on the other hand, enables a whole host of EJB functionality by acting as the central service registry for a Java EE container. One of the major enhancements in EJB 3, dependency injection (DI), is simply a wrapper over JNDI lookups.

In this appendix we offer a brief primer on both of these technologies, especially as they relate to EJB 3.

A.1. Distributed communication with RMI

Java RMI made its debut very early in Java’s history. (also known Java Remote Method Protocol (JRMP)). It became clear that, as a platform that touts the distributed computing mantra “The network is the computer,” Java must provide a simple and robust mechanism for communication across JVM instances. Ideally, method calls on an object running in one JVM should be executed transparently in another remote JVM. With the help of a small amount of boilerplate setup code combined with a few code-generation tools, RMI delivers on this promise. To invoke an object method remotely using RMI, you must

  • Register the remote object implementing the method with an RMI registry.
  • Look up the remote object reference from the registry (the remote reference is accessed through an interface).
  • Invoke the remote method through the reference obtained from the registry.

The idea of a registry to store remote object references is central to RMI. All objects that need to be invoked remotely must be registered with an RMI registry. In order to be registered with an RMI server and be invoked remotely, an object must extend a few RMI classes as well as implement a remote interface. A remote interface defines the object methods that can be invoked remotely. Like the target remote object itself, a remote interface must extend a few RMI interfaces.

Once a remote object is registered with a registry, any client that can access the registry can obtain a reference to it. To get a reference to a remote object, the client must retrieve it from the registry by name and then invoke it through the remote interface. Each time a client invokes a method through the remote interface, the method invocation request is carried transparently across the wire to the remote object. The remote object method is then executed inside its own JVM. The result of the method invocation (such as a return value) is transported back across the network to be delivered to the client by RMI.

To better understand what is happening in the remote interface scenario, let’s draw an analogy with something familiar to all of us: a television. Think of a television as the remote object to be called. The remote control for our television is the remote interface. The infrared protocol sent by the remote control and picked up by the television is like the RMI protocol. While using the remote control, we don’t have to know all of the details of how the infrared signal works; all we need to know is what each of the buttons on the remote does (the interface methods). Figure A.1 shows how a typical RMI invocation scenario works.

Figure A.1. Communication between the RMI client and server (remote object). The RMI server binds an object in the RMI registry. The client performs a lookup in the RMI registry to get a reference to the remote object and then invokes the method on the remote object through the remote interface.

The parallels between EJB and RMI are obvious. Like the EJB object discussed in chapter 5, RMI works through proxies on both the client and remote object endpoints to transparently provide distributed computing as a service. In the EJB world, the business interface plays the same role as the “remote” interface, while the EJB bean itself is the “remote object.” Just as RMI handles the communication details between the remote client and the object, the container handles the communication details between the EJB client and the managed bean. Before EJB 3, the linkages between RMI and EJB were even more obvious—for example, remote business interfaces had to put java.rmi.RemoteException in their throws clause. In EJB 3, the fact that EJB uses RMI for remoting is rightfully hidden far behind the API as an implementation detail you can safely ignore.

When you annotate a session bean business interface using the @Remote annotation as we did in chapters 2 and 3, the container automatically makes the bean callable via RMI as a remote object. Similarly, when you inject a remote bean using the @EJB annotation, the container talks to the bean through RMI under the hood.

The greatest benefit for RMI is that it provides location transparency. The client invokes methods on a remote object as if the object were located in the same virtual machine, without having to worry about the underlying plumbing. This also means that RMI is generally a lot easier to use and flexible compared to writing TCP/IP sockets by hand for remote communication.

As compelling as RMI might seem from our very high-level discussion, like all technologies it has its own set of problems.

Remote object invocation uses pass-by-value semantics. When the client passes a parameter to the remote method, RMI sends the data held by the object across the network as a byte stream. The byte stream is received by RMI on the other end of the communication tunnel, copied into an object of the same type as passed in by the client, and weaved in as a parameter to the remote object. Objects returned by the remote method go through the same translation-transport-translation steps. The process of turning an object into a byte stream is called marshaling, and the process of turning a byte stream into an object is called unmarshaling. This is exactly why all objects transported across the network by RMI must implement the java.io.Serializable interface. For large objects, the cost of marshaling and unmarshaling can be pretty high, making RMI a performance bottleneck compared to local invocation. This is why you want to make sure that objects passed over RMI are not large, because they can slow down your application.

Like EJB 2, RMI isn’t too easy to use. You often find yourself performing functions such as extending obscure interfaces or classes, and following a strange programming model. Luckily, EJB 3 does all the hard work of generating RMI code behind the scenes.

Last but not least, RMI is great for Java-to-Java transparent communication but is not good for interoperability with Microsoft .NET and the like. If interoperability is a concern, you should be using web services instead of RMI. Note, however, web services is overkill if not absolutely needed, primarily because text-based, parsing-intensive XML performs much worse than binary-based protocols like RMI. Moreover, it is possible to use RMI-IIOP to interoperate between Java and CORBA components written in languages like C++. Every EJB container must support RMI-IIOP and enabling RMI-IIOP is simply a matter of configuration.

If you want to learn more about RMI, check out http://java.sun.com/products/jdk/rmi/.

A.2. JNDI as a component registry

JNDI is the JDBC of naming and directory services. Just as JDBC provides a standard Java EE API to access all kinds of databases, JNDI standardizes naming and directory service access. If you’ve ever used a Lightweight Directory Access Protocol (LDAP) such as a Microsoft Active Directory server, you already know what a naming and directory service is.

In simple terms, a naming service provides the ability to locate a component or service by name. You give a naming service the complete name for a resource and it figures out how to get you a handle to the resource that you can use. Domain Name Service (DNS) is a relatively well-known example of a naming service. When we point our web browser to http://yahoo.com, the DNS server conducts a lookup and directs us to the right IP address for Yahoo. The RMI registry is another example of a naming service. In a sense, even an operating system file manager is a naming service. You give the file manager the complete path to a file and it gives you a handle to the file you are looking for.

As figure A.2 shows, JNDI provides a uniform abstraction over a number of different naming services such as LDAP, DNS, Network Information Service (NIS), Novell Directory Services (NDS), RMI, Common Object Request Broker Architecture (CORBA), and so on. Once you have an instance of a JNDI context, you can use it to locate resources in any underlying naming service available to the context. Under the hood, JNDI negotiates with each available naming service given the name of a resource to figure out where to look up the service’s actual location.

Figure A.2. JNDI provides a single unified API to access various naming services such as LDAP, NDS, NDS, NIS, RMI, and CORBA. Any naming service with a JNDI Service Provider Interface (SPI) provider can be plugged into the API seamlessly.

Like RMI, JNDI plays a vital role in EJB 3, although it is by and large hidden behind the scenes (also like RMI, JNDI used to be a lot more visible and made EJB much more cumbersome as of EJB 2). In a very real sense, JNDI is to EJB what the RMI registry is to RMI. JNDI is used as the central repository for resources managed by the container.

As a result, every bean managed by the container is automatically registered with JNDI. In addition, a typical container JNDI registry will store JDBC data sources, JMS queues, JMS connection factories, JPA entity managers, JPA entity manager factories, and so on. Whenever a client (such as an EJB) needs to use a managed resource, they use JNDI to look up the resource by its unique name. Figure A.3 shows how a typical JNDI tree for a Java EE application server might look.

Figure A.3. An example JNDI tree for an application server. All global resources such as jdbc and jms are bound to the root context of JNDI tree. Each application has its own application context, and EJBs and other resources in the application are bound under the application context.

As you can see in figure A.3, resources are stored in a JNDI tree in a hierarchical manner. This means that JNDI resource names look much like Unix file pathnames (they also sometimes start with a protocol specification such as java:, much like a URL address you would enter in a browser navigation bar). As with RMI, once you procure a handle to a resource from a JNDI context, you can use it as though it were a local resource.

To use a resource stored in the JNDI context, a client has to initialize the context and look up the resource. Despite the robustness of the JNDI mechanism itself, the code to do so isn’t that intimidating. The code in listing A.1 looks up a JDBC data source from JNDI and creates a new connection from it. As you might imagine, the JDBC connection then might be used to issue SQL to the underlying database pointed to by the retrieved data source.

Listing A.1. Looking up a JDBC data source using JNDI
Context context = new InitialContext();
DataSource dataSource =
(DataSource)context.lookup("java:comp/env/jdbc/ActionBazaarDS");
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();

In listing A.1, the JNDI lookup takes place in the first two lines. First, an InitialContext object is instantiated. The InitialContext object connects to any given JNDI tree. In the case of the parameter-less version of the constructor used in listing A.1, the InitialContext object connects to the “default” JNDI tree. The JNDI defaults are determined by the contents of a file named jndi.properties that can be stored anywhere in the JVM’s CLASSPATH. The Java EE application server usually provides this properties file, and the settings in the file typically point to the JNDI tree of the local application server. As a result, the default InitialContext constructor is most useful while looking up resources within the same JVM. If you are looking up a resource (such as an EJB) on a remote application server, then you must feed environment properties to the InitialContext constructor. This can be done as follows:

Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"oracle.j2ee.rmi.RMIInitialContextFactory");
properties.put(Context.PROVIDER_URL,
"ormi://192.168.0.6:23791/appendixa");
properties.put(Context.SECURITY_PRINCIPAL, "oc4jadmin");
properties.put(Context.SECURITY_CREDENTIALS, "welcome1");
Context context = new InitialContext(properties);

In the example, the custom Properties entries specify that we are trying to connect to a remote Oracle application server JNDI tree. Note JNDI connection properties are vendor (application server) specific and our example cannot be used universally, so you should consult with your application server’s documentation to see how you can connect to it remotely. In general, you might find that most application servers require a common set of JNDI properties defined as constants in the Context interface. Table A.1 summarizes the most common environment properties that are used for Java EE application servers.

Table A.1. Common JNDI environment properties required for creating an initial context to connect to a remote JNDI service provider in a Java EE environment. These are specified either as system properties in the jndi.properties file in the JVM at the client side or as Property object entries passed to the constructor in your Java code. Of these options, a properties file is recommended as it improves maintainability of your application code.

Property Name

Description

Example Value

java.naming.factory.initial

The name of the factory class that will be used to create the context

oracle.j2ee.rmi.RMIInitialContextFactory

java.naming.provider.url

The URL for the JNDI service provider

ormi://localhost:23791/chapter1

java.naming.security.principal

The username or identity for authenticating the caller in the JNDI service provider

oc4jadmin

java.naming.security.credentials

The password for the username/principal being used for authentication

welcome1

Note that instead of providing environment properties programmatically, you can also simply modify the jndi.properties file in your runtime CLASSPATH. If you are using EJB 3 DI, this is the only way of connecting to a remote server.

In the second line of listing A.1, the lookup is performed using the context instantiated in the first line. The single parameter of the lookup method is the full name of the resource you are seeking. In our case, the JNDI name of the JDBC data source we are looking for happens to be jdbc/ActionBazaarDS. Note that because the lookup method returns the Object type, we must cast the retrieved resource to the correct type. In the case of EJBs, references returned by JNDI must be cast to a valid business interface implemented by the EJB.

While the code in listing A.1 looks harmless, don’t be taken in by appearances. JNDI lookups were one of the primary causes for EJB 2’s complexity. First of all, you had to do lookups to access any resource managed by the container, even if you were only accessing data sources and EJBs from other EJBs located in the same JVM. Given that most EJBs in an application depend on other EJBs and resources, imagine the lines of repetitive JNDI lookup code littered across an average business application! To make matters worse, JNDI names of resources aren’t always that obvious to figure out, especially for resources that are bound to the environment naming context (which must use the arcane java:comp/env/ prefix for portability of applications instead of using a global JNDI name).

The good news is that except for certain corner cases, you won’t have to deal with the evils of JNDI in EJB 3. EJB 3 puts the mechanical details of JNDI lookups well hidden behind metadata-based DI. DI does such a great job in abstraction that you won’t even know that JNDI lookups are happening behind the scenes, even for remote lookups. DI is discussed in chapters 2, 3, 4, and 5.

You can find more about JNDI from Sun’s website at http://java.sun.com/products/jndi/.