IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
EJB best practices: Industrial-strength JNDI optimization
64KBe-mail it!
Contents:
Reducing context instances
Optimizing lookups
Inside the EJBHomeFactory class
Resources
About the author
Rate this article
Related content:
EJB best practices series
EJB fundamentals tutorial
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Use caching and a generic factory class to automate JNDI lookups

Level: Intermediate

Brett McLaughlin (mailto:brett@oreilly.com?cc=&subject=Industrial-strength JNDI optimization)
Author and Editor, O'Reilly and Associates
1 September 2002

Column iconBrett McLaughlin continues his EJB best practices with an examination of JNDI lookups, which are an essential and frequent part of almost all EJB interactions. Unfortunately, JNDI operations almost always exact a performance toll. In this tip, Brett shows you how a home-interface factory can reduce the overhead of JNDI lookups in your EJB applications.

Every kind of EJB component (session, entity, and message driven) has a home interface. The home interface is a bean's base of operations; once you've found it, you have access to that bean's functionality. EJB applications rely on JNDI lookups to access their beans' home interfaces. Because EJB apps tend to run multiple beans, and because JNDI lookups are often present in many components, much of an application's performance overhead can be spent on these lookups.

In this tip, we'll look at some of the most common JNDI optimizations. In particular, I'll show you how to combine caching and a generic helper class to create a factory-style solution to JNDI overhead.

Reducing context instances
Listing 1 shows a typical piece of EJB code, requiring multiple JNDI lookups. Study the code for a moment, and then we'll work on optimizing it for better performance.


public boolean buyItems(PaymentInfo paymentInfo, String storeName,
List items) {
      // Load up the initial context
      Context ctx = new InitialContext();

      // Look up a bean's home interface
      Object obj = ctx.lookup("java:comp/env/ejb/PurchaseHome");
      PurchaseHome purchaseHome =
       (PurchaseHome)PortableRemoteObject.narrow(obj, PurchaseHome.class);
      Purchase purchase = purchaseHome.create(paymentInfo);

      // Work on the bean
      for (Iterator i = items.iterator(); i.hasNext(); ) {
          purchase.addItem((Item)i.next());
      }

      // Look up another bean
      Object obj = ctx.lookup("java:comp/env/ejb/InventoryHome");
      InventoryHome inventoryHome =
       (InventoryHome)PortableRemoteObject.narrow(obj, InventoryHome.class);
      Inventory inventory = inventoryHome.findByStoreName(storeName);

      // Work on the bean
      for (Iterator i = items.iterator(); i.hasNext(); )
          inventory.markAsSold((Item)i.next());
      }

      // Do some other stuff
}

While this example is somewhat contrived, it does reveal some of the most glaring problems with using JNDI. For starters, you might ask yourself if the new InitialContext object is necessary. It's likely that this context has already been loaded elsewhere in the application code, yet we've created a new one here. Caching the InitialContext instances would result in an immediate performance boost, as shown in Listing 2:


public static Context getInitialContext() {
      if (initialContext == null) {
          initialContext = new InitialContext();
      }

      return initialContext;
}

By using a helper class with the getInitialContext() instead of instantiating a new InitialContext for every operation, we've cut down the number of contexts floating around in our application to one.

Uh oh -- what about threading?

If you're worried about the effects of threading on the solution proposed here, don't be. It is absolutely possible that two threads could go to work on at the same time (thus creating two contexts at once) but this type of error would happen only on the first invocation of the method. Because the problem won't come up more than once, synchronization is unnecessary, and would in fact introduce more complexities than it would resolve.

Optimizing lookups
Caching the context instances is a step in the right direction, but we're not done optimizing yet. Every time we call the lookup() method it will perform a new lookup, and return a new instance of a bean's home interface. At least, that's the way JNDI lookups are usually coded. But wouldn't it be better to have just one home-interface per bean, shared across components?

Rather than looking up the home interface for PurchaseHome or InventoryHome again and again, we could cache each individual bean reference; that's one solution. But what we really want is a more general mechanism for caching home interfaces in our EJB applications.

The answer is to create a generic helper class to both obtain the initial context and look up the home interface for every bean in the application. In addition, this class should be able to manage each bean's context for various application components. The generic helper class shown in Listing 3 will act as a factory for EJB home interfaces:


package com.ibm.ejb;

import java.util.Map;
import javax.ejb.EJBHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class EJBHomeFactory {

      private static EJBHomeFactory instance;

      private Map homeInterfaces;
      private Context context;

      // This is private, and can't be instantiated directly
      private EJBHomeFactory() throws NamingException {
          homeInterfaces = new HashMap();

          // Get the context for caching purposes
          context = new InitialContext();

          /**
           * In non-J2EE applications, you might need to load up
           *   a properties file and get this context manually. I've
           *   kept this simple for demonstration purposes.
           */
      }

      public static EJBHomeFactory getInstance() throws NamingException {
          // Not completely thread-safe, but good enough 
          // (see note in article)
          if (instance == null) {
              instance = new EJBHomeFactory();
          }
          return instance;
      }

      public EJBHome lookup(String jndiName, Class homeInterfaceClass) 
            throws NamingException {

          // See if we already have this interface cached
          EJBHome homeInterface = 
            (EJBHome)homeInterfaces.get(homeInterfaceClass);
          // If not, look up with the supplied JNDI name
          if (homeInterface == null) {
              Object obj = context.lookup(jndiName);
              homeInterface =
               (EJBHome)PortableRemoteObject.narrow(obj, homeInterfaceClass);

              // If this is a new ref, save for caching purposes
              homeInterfaces.put(homeInterfaceClass, homeInterface);
          }
          return homeInterface;
      }
}

Inside the EJBHomeFactory class
The key to the home-interface factory is in the homeInterfaces map. The map stores each bean's home interface for use; as such, one home-interface instance can be used over and over again. You should also note that the key in the map is not the JNDI name passed into the lookup() method. It's quite common to have the same home interface bound to different JNDI names, but doing so can result in duplicates in your map. By relying on the class itself, you ensure that you won't end up with multiple home interfaces for the same bean.

Inserting the new home-interface factory class into the original code from Listing 1 will result in the optimized EJB lookup shown in Listing 4:


public boolean buyItems(PaymentInfo paymentInfo, String storeName,
List items) {

      EJBHomeFactory f = EJBHomeFactory.getInstance();

      PurchaseHome purchaseHome =
          (PurchaseHome)f.lookup("java:comp/env/ejb/PurchaseHome", 
          PurchaseHome.class);
      Purchase purchase = purchaseHome.create(paymentInfo);

      // Work on the bean
      for (Iterator i = items.iterator(); i.hasNext(); ) {
          purchase.addItem((Item)i.next());
      }

      InventoryHome inventoryHome =
          (InventoryHome)f.lookup("java:comp/env/ejb/InventoryHome", 
          InventoryHome.class);
      Inventory inventory = inventoryHome.findByStoreName(storeName);

      // Work on the bean
      for (Iterator i = items.iterator(); i.hasNext(); ) {
          inventory.markAsSold((Item)i.next());
      }

      // Do some other stuff
}

In addition to being more clear (at least in my opinion) the factory-optimized EJB lookup above will perform much faster over time. The first time you use the new class, you'll incur all the usual lookup penalties (assuming another portion of the application hasn't already paid them) but all future JNDI lookups should hum right along. It's also worth pointing out that the home-interface factory will not interfere with your container's bean management. Containers manage bean instances, not the home interfaces of those instances. Your container will still be in charge of instance swapping, as well as any other optimizations you want it to perform.

In the next installment of EJB best practices, I'll show you how you can enable administrative access to entity beans, without directly exposing them to your application's Web tier. Until then, I'll see you online.

Resources

  • Read all the tips in the EJB best practices series.

  • Sun Microsystems's EJB technology home page is a good resource for all things related to EJB technology.

  • TheServerSide offers lots of articles and information pertaining to J2EE.

  • The jGuru EJB fundamentals tutorial (developerWorks, March 2001) provides a comprehensive introduction to Enterprise JavaBeans technology with particular attention to the role of EJB components in distributed-computing scenarios, the architecture, the extension APIs, and the fundamentals of working with EJB technologies.

  • Visit the developerWorksJava tutorials page for a listing of other free EJB- and J2EE-related tutorials.

  • You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.

About the author
Photo of Brett McLaughlin Brett McLaughlin has been working in computers since the Logo days (remember the little triangle?). He currently specializes in building application infrastructure using Java and Java-related technologies. He has spent the last several years implementing these infrastructures at Nextel Communications and Allegiance Telecom, Inc. Brett is one of the co-founders of the Java Apache project, Turbine, which builds a reusable component architecture for Web application development using Java servlets. He is also a contributor of the EJBoss project, an open source EJB application server, and Cocoon, an open source XML Web-publishing engine.


64KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact