J2EE or J2SE? JNDI works with both

The Java Naming and Directory Interface is not just for J2EE applications

JNDI, the Java Naming and Directory Interface, allows applications to access various naming and directory services via a common interface. The figure below shows the JNDI architecture. Like JDBC (Java Database Connectivity), JNDI is not a service, but a set of interfaces; it allows applications to access many different directory service providers using a standardized API. Just as with JDBC, the JDK contains the JNDI interfaces but does not include a JNDI service provider -- although Sun Microsystems provides adapters for connecting to existing directory service providers, such as LDAP (lightweight directory access protocol), DNS (domain name service), and CORBA. However, you can use one of several free or open source JNDI providers in your J2SE (Java 2 Platform, Standard Edition) applications.

JNDI architecture. Source: Sun Microsystems.

JNDI is the glue that holds together J2EE (Java 2 Platform, Enterprise Edition) applications. JNDI was designed to support highly dynamic application assembly and deployment, with components constantly being added and updated without rebuilding the entire system. A naming service helps organize an enterprise application by acting as a central registry for components. J2EE applications typically use JNDI in several ways:

  • As a means to store application configuration information in a centralized, hierarchical database
  • As a repository for live objects shared among application components, which can run in different JVMs or on different systems
  • As an interface to existing directory services like LDAP (using a provider specific to that external service)
  • As a lightweight, hierarchical database for storing transient application state

Like J2EE applications, larger or more dynamic J2SE applications can benefit from the loose coupling and dynamic binding offered by an active directory service.

A simple JNDI example

Storing and retrieving objects from a JNDI namespace is quite straightforward; you first obtain a JNDI naming context and then use the bind() and lookup() methods to store and retrieve objects, as Listing 1 shows:

Listing 1. Store and retrieve objects from a JNDI namespace

  import javax.naming.*;
  public void createName() throws NamingException {
    Context context = new InitialContext();
    context.bind("/config/applicationName", "MyApp");
  }
  public String getName() throws NamingException {
    Context context = new InitialContext();
    return (String) context.lookup("/config/applicationName");
  }  

Listing 1 demonstrates the most common JNDI operations -- creating a JNDI context, binding an object in the context, and retrieving an object from the context. Note that the JNDI namespace and the client might reside in different JVMs, and the bind() and lookup() invocations might likewise occur in different JVMs. A JNDI provider uses a variety of techniques, including serialization, to ensure that the object can move from JVM to JVM and still be retrieved in its original form.

Listing 1's code fragments have several hidden assumptions: How does JNDI know which provider to use when creating the context? For providers that require authentication, where do the credentials come from? Generally, you specify the provider and other connection parameters by setting several JNDI-specific properties in the system properties, or by setting them in the jndi.properties file. Also, any given provider might not be able to bind or retrieve arbitrary objects. For example, some providers, such as the DNS provider, are read-only, and some providers might be more flexible about the types of objects they can bind. Most vendor-supplied JNDI providers can store and retrieve objects that implement one of java.io.Serializable, java.rmi.Remote, or javax.naming.Referenceable.

The hidden JNDI provider

The JNDI providers shown in the previous figure all have something in common -- they delegate requests to an external directory service, such as LDAP. However, this diagram doesn't show one important type of JNDI provider: the JNDI provider built into every J2EE container, which stores directory information in an internal database. When we talk about using JNDI in J2EE applications, we usually refer to this container-supplied provider.

How does J2EE use JNDI?

J2EE applications are assembled out of components. The ways components are configured and connected to each other are specified at deployment time; much of this information is stored in a JNDI namespace. J2EE applications use JNDI to store configuration information (such as string and numeric constants), stateless objects (including object factories), and EJB (Enterprise JavaBean) home interfaces.

JNDI is the glue that allows you to build J2EE applications out of components like servlets, JSPs (JavaServer Pages), and EJBs. In a J2EE application, each component finds other components not via static linking, but through JNDI lookups. J2EE applications allow for deployment-time binding while maintaining type and link safety by having each component export a list of external components and resources it needs. The deployer ensures that each import has a corresponding component of the correct type in the application. J2EE containers include tools to help you do this correctly.

How can J2SE apps use JNDI?

Like J2EE applications, J2SE applications can use JNDI as a shared repository of named configuration parameters, objects, and object factories. The Factory creation pattern works especially well with JNDI, as you can just store your factories in the JNDI namespace. J2SE applications can also use JNDI as a more robust, feature-rich, and centralized replacement for the RMI (Remote Method Invocation).

Most J2SE applications load their configuration information from configuration files, which are stored either as property files or XML documents. These configuration files specify all sorts of configuration information; some more sophisticated applications store information for instantiating objects, such as class name and constructor parameters, in a configuration file.

Using JNDI to store constants, objects, and object factories has several advantages over the more traditional configuration mechanism. Since most JNDI providers are network-accessible, you do not need to keep a consistent set of configuration files for each host in a distributed application. Additionally, using reflection to instantiate an object is messy and requires more code (more error-recovery code in particular) than simply retrieving the object out of a JNDI namespace. While using JNDI doesn't generally obviate the need to instantiate an object from configuration information (when you populate the namespace, generally at application startup time), this complexity is factored away from most of the application code, which can simply retrieve the desired object from the JNDI namespace.

Another reason to use JNDI instead of configuration files in a distributed application is that a configuration file's information might be sensitive, such as a password for database access. By storing a DataSource in the JNDI namespace, you can make the database connection available to the entire application without making the password available to the entire application. (For example, the PoolMan package, a widely used open source connection-pooling package, stores a DataSource object in the JNDI namespace if one is available.)

To use JNDI with your J2SE applications, you'll first need a JNDI provider, since the JDK doesn't include one. If you just want to use JNDI to access external directories such as LDAP, you can use one of the Sun-supplied providers. But usually, you'll want a standalone provider, and there are several you can use.

A standalone JNDI provider

The JBoss open source J2EE server includes a JNDI provider (JNP) that can run as a standalone service; it provides an excellent lightweight network-accessible JNDI service. JNP uses an in-memory database to store objects, so the namespace's contents do not persist across service restarts. You can easily run JNP by itself or configure the JBoss container to start only the JNP service.

Option 1. Configure the JNP server as a JBoss service:

The JBoss application server is built on the JMX (Java Management Extensions) framework; the framework allows application services to be modularized into JMX mbeans, which can be started and managed independently. The file jboss.jcml contains a list of mbeans that will load when the container starts. JBoss's default configuration includes more than 50 mbeans, but if you specify that jboss.jcml should contain only the single declaration shown in Listing 2 and then start the JBoss server, the server will load only the JNDI provider and no other J2EE application services:

Listing 2. JNDI-only jboss.jcml file

<?xml version="1.0" encoding="UTF-8"?>
<server>
  <mbean code="org.jboss.naming.NamingService" 
         name="DefaultDomain:service=Naming">
    <attribute name="Port">1099</attribute>
  </mbean>
</server>

Option 2. Configure the JNP server to stand alone

JNP can also run as a standalone server application. To configure it as such, you need two jar files:

  • jnpserver.jar -- from the JBoss distribution, in the lib/ext directory
  • log4j.jar -- the widely used logging facility, from the Apache Jakarta project; you will also find it in the JBoss distribution in the lib/ext directory

In addition, you need a

log4j.properties

file; Listing 3 shows a simple one. This file should be accessible via the classpath:

Listing 3. Simple log4j.properties file

 # Use a ConsoleAppender -- write log data to standard out
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

To run the JNP server in standalone mode, ensure the log4j.jar file, the jnpserver.jar file, and the directory containing log4j.properties are all on your classpath. Then, start the JNP server as follows:

   java org.jnp.server.Main

You can also use the org.jnp.server object to start the JNDI service within your application's JVM.

Use the JNDI service

Once the JNP server is running, you can configure your application to use JNP by including the jnp-client.jar file in its classpath and specifying the java.naming.provider.url and java.naming.factory.initial properties in either the system properties or the jndi.properties file. Listing 4 shows an example jndi.properties file:

Listing 4. jndi.properties file

 java.naming.provider.url=jnphost.mycompany.com:1099
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming

The following examples show how you could populate a JNDI namespace at application startup time with string-, integer-, and object-valued parameters specified in a properties file, and how an application component could retrieve those properties. For brevity, Listing 5a assumes the existence of methods to parse the XML configuration file and omits the error-handling code:

Listing 5a. Load a JNDI namespace from a config file

   public void loadJNDI() {
    Context context = new InitialContext();
    ConfigItem[] items = getConfigItems();
    for (int i=0; i<items.length; i++) {
      Object o=null;
      if (items[i].getType().equals("Integer"))
        o = Integer.decode(items[i].getValue());
      else if (items[i].getType().equals("String"))
        o = items[i].getValue();
      else if (items[i].getType().equals("Object"))
        o = Class.forName(items[i].getValue()).newInstance();
      context.bind(items[i].getName(), o);
    }
  }

Listing 5b. Example XML config file

   <config>
    <item name="config/screen/resolutionX" type="Integer" 
          value="1024" />
    <item name="config/screen/resolutionY" type="Integer" 
          value="768" />
    <item name="converters/html" type="Object" 
          value="com.mycompany.converters.HtmlRenderer" />
    <item name="converters/pdf" type="Object" 
          value="com.pdfmonger.PdfRenderer" />
  </config>

Listing 5c. Retrieve and use an object from JNDI

   public void convert(InputStream in, OutputStream out) {
    // Retrieve the converter object from JNDI
    Context context = new InitialContext();
    Renderer renderer = (Renderer) context.lookup("converters/html");
    // Use the converter object
    renderer.convert(in, out);
  }

As you can see, retrieving objects from JNDI is quite painless and straightforward. By using JNDI to store configuration information, stateless objects, or object factories, you can easily build flexible applications while containing the configuration complexity in a single place, even for distributed applications. (If your components access objects from a JNDI namespace, document these dependencies in the component's Javadoc.)

JNDI is not just for J2EE anymore

Even though the JNDI client interfaces are part of the J2SE distribution, most J2SE applications do not use JNDI. Those that do generally use JNDI only to access external directory services such as LDAP. However, J2SE applications can also use the deployment-time binding features that only J2EE applications have typically used thus far. With JNDI provider implementations like JNP freely available, any application that needs a naming service can have one.

Brian Goetz is a professional software developer with more than 15 years' experience. He is a principal consultant at Quiotix, a software development and consulting firm located in Los Altos, Calif.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies