|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Use caching and a generic factory class to automate JNDI
lookups
Brett
McLaughlin (mailto:brett@oreilly.com?cc=&subject=Industrial-strength
JNDI optimization) Author and Editor, O'Reilly and Associates 1
September 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.
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 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. |
|
|