Service Locator

Brief Description

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.

Detailed Description

Detailed Example

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 :

  • Clients use ServiceLocator to access services.

    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.

  • Public methods look up distributed resources.

    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

    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
  • Improving performance with the Singleton pattern and caching.

    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.

Copyright © 2002 Sun Microsystems, Inc. All Rights Reserved.
Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve
JavaOne Banner
Java 8 banner (182)