Communities
|
Social Applications
Networks
Support
|
|
C-Level Executives
Other Roles
|
|
Support
Education
Partner
Other Tasks
|
Enterprise applications require a way to look up the service objects that provide access to distributed components. Java TM 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 JavaBeans
TM
(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, JDBC TM 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.

