Friday, September 7, 2007

EJB best practices

EJB best practices: Industrial-strength JNDI optimization

Use caching and a generic factory class to automate JNDI lookups
developerWorks

Integrate new tools and architectures into your environment -- fast!

Level: Intermediate

Brett McLaughlin (brett@newInstance.com), Author and Editor, O'Reilly Media Inc.

01 Sep 2002

Brett 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.

No comments:

Google