Enterprise applications require a way to look up the service objects that provide access to distributed components. JavaTM 2 Platform, Enterprise Edition (J2EE) applications use Java Naming and Directory Interface (JNDI) to look up enterprise bean home interfaces, Java Message Service (JMS) components, data sources, connections, and connection factories. Repetitious lookup code makes code difficult to read and maintain. Furthermore, unnecessary JNDI initial context creation and service object lookups can can cause performance problems.
The Service Locator pattern centralizes distributed service object lookups, provides a centralized point of control, and may act as a cache that eliminates redundant lookups. It also encapsulates any vendor-specific features of the lookup process.
Core J2EE Patterns
The Java Pet Store sample application, v1.3.1 has two service locators: a Web-tier
class
ServiceLocator
, and an Enterprise JavaBeansTM (EJB) tier class, also called
ServiceLocator
.
Both classes manage lookup and caching of enterprise
bean home interfaces, JMS and database connection factories,
and environment entries
within their respective tiers. The only difference between them
is that the Web-tier class is a singleton, and it caches the
objects it looks up. The EJB-tier class is not a singleton,
and does not cache.
The following code discussion uses examples from the
Web-tier
ServiceLocator
:
The sample application class
AdminRequestBD
is a business delegate that uses the Web-tier
ServiceLocator
to access the order processing center enterprise bean
OPCAdminFacade
.
(See the
Business Delegate
design pattern for a more detailed description of
AdminRequestBD.)
Figure 1 is a structure diagram that demonstrates
how
AdminRequestBD uses
ServiceLocator
to find the remote home interface of the
OPCAdminFacade
enterprise bean. The
ServiceLocator returns a
the remote enterprise bean interface
OPCAdminFacadeHome,
by either retrieving it from the cache or looking
it up using an internal
InitialContext instance.
The client then uses the
OPCAdminFacade to find
or create a remote component interface to an
OPCAdminFacade
|
| Figure 1. Structure diagram of ServiceLocator sample code |
In the following code excerpt,
AdminRequestBD calls the
ServiceLocator static method
getInstance
to get the singleton instance of the service locator, then calls
getRemoteHome to get the remote home interface
of the
OPCAdminFacade enterprise bean. Notice
that the caller must typecast the remote home interface
to
OPCAdminFacadeHome because
getRemoteHome
returns type
EJBHome
.
public class AdminRequestBD {
...
public AdminRequestBD() throws AdminBDException {
try {
OPCAdminFacadeHome home =
(OPCAdminFacadeHome) ServiceLocator.getInstance().getRemoteHome(OPC_ADMIN_NAME, OPCAdminFacadeHome.class);
opcAdminEJB = home.create();
} catch (ServiceLocatorException sle) {
...
}
}
The service locator greatly simplifies the lookup of the
enterprise bean home interface. The singleton and caching
strategies (discussed below) also improve performance, because
they avoid constructing unnecessary
InitialContext
and enterprise bean home interfaces.
The public methods of the service locator look up distributed resources by their JNDI names. There are methods that find and return enterprise bean local home interfaces, JDBCTM data sources, JMS queues and topics, and JMS queue and topic connection factories. There are also convenience methods that look up and perform type conversions on environment entries.
As an example, method
getLocalHome
(for finding enterprise bean local home interfaces) appears below.
Each method that locates a particular type of resource
returns either a cached reference to the requested resource,
or uses JNDI to find the resource, placing a reference in
the cache before returning it.
// Enterprise bean lookups
public EJBLocalHome getLocalHome(String jndiHomeName)
throws ServiceLocatorException {
EJBLocalHome home = null;
try {
if (cache.containsKey(jndiHomeName)) {
home = (EJBLocalHome) cache.get(jndiHomeName);
} else {
home = (EJBLocalHome) ic.lookup(jndiHomeName);
cache.put(jndiHomeName, home);
}
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
} catch (Exception e) {
throw new ServiceLocatorException(e);
}
return home;
}
Methods that return enterprise bean home interface references
are only type-safe to the platform interface
level; for example,
getLocalHome returns a
EJBLocalHome, but the client must typecast
the result.
Method
getRemoteHome is similar to
getLocalHome, except that it returns an
enterprise bean remote, instead of local, home interface.
It also requires a reference to a class object for the
specific remote home interface, because remote home
lookups use method
PortableRemoteObject.narrow
to perform the type conversion from the object returned
from the JNDI lookup to the actual home interface type.
The client that calls
getRemoteHome must still
typecast the result to the remote home interface type, as
shown in the first example above.
public EJBHome getRemoteHome(String jndiHomeName, Class className)
throws ServiceLocatorException {
EJBHome home = null;
try {
if (cache.containsKey(jndiHomeName)) {
home = (EJBHome) cache.get(jndiHomeName);
} else {
Object objref = ic.lookup(jndiHomeName);
Object obj = PortableRemoteObject.narrow(objref, className);
home = (EJBHome)obj;
cache.put(jndiHomeName, home);
}
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
} catch (Exception e) {
throw new ServiceLocatorException(e);
}
return home;
}
As mentioned above, the service locator returns JMS resources, JDBC data sources, and performs type conversion on values in environment entries. The table below summarizes the names and return types of these methods.
Table 1. Additional ServiceLocator methods
| Method Name |
Return Type |
Resource Type |
|---|---|---|
getQueueConnectionFactory
|
QueueConnectionFactory
|
JMS |
getQueue
|
Queue
|
JMS |
getTopicConnectionFactory
|
TopicConnectionFactory
|
JMS |
getTopic
|
Topic
|
JMS |
getDataSource
|
DataSource
|
JDBC |
getUrl
|
URL
|
env-entry |
getBoolean
|
boolean
|
env-entry |
getString
|
String
|
env-entry |
The Singleton pattern [
GHJV95
] ensures that
only a single instance of a class exists in an application.
The meaning of the term "singleton" is not always clear
in a distributed environment; in
ServiceLocator
it means that only one instance of the class exists per class loader.
The Singleton pattern improves performance because it
eliminates unnecessary construction of
ServiceLocator
objects, JNDI
InitialContext objects, and
enables caching (see below).
The Web-tier service locator also improves performance by caching the objects it finds. The cache lookup ensures that a JNDI lookup only occurs once for each name. Subsequent lookups come from the cache, which is typically much faster than a JNDI lookup.
The code excerpt below demonstrates how the
ServiceLocator
improves performance with the Singleton pattern and an object cache.
public class ServiceLocator {
private InitialContext ic;
private Map cache;
private static ServiceLocator me;
static {
try {
me = new ServiceLocator();
} catch(ServiceLocatorException se) {
System.err.println(se);
se.printStackTrace(System.err);
}
}
private ServiceLocator() throws ServiceLocatorException {
try {
ic = new InitialContext();
cache = Collections.synchronizedMap(new HashMap());
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}
}
static public ServiceLocator getInstance() {
return me;
}
A private class variable
me contains a reference
to the only instance of the
ServiceLocator class.
It is constructed when the class is initialized in the static
initialization block shown. The constructor initializes the instance
by creating the JNDI
InitialContext
and the
HashMap that is used
a cache. Note that the no-argument constructor
is
private: only class
ServiceLocator
can construct a
ServiceLocator. Because only
the static initialization block creates the instance, there
can be only one instance per class loader.
Classes that use service locator access the singleton
ServiceLocator instance
by calling public method
getInstance.
Each object looked up has a JNDI name which, being unique, can
be used as a cache
HashMap key for the object.
Note also that the
HashMap used as a cache is
synchronized so that it may be safely accessed from multiple
threads that share the singleton instance.