Context
Service lookup and creation involves complex interfaces and network operations.
Problem
J2EE clients interact with service components, such as Enterprise JavaBeans
(EJB) and Java Message Service (JMS) components,
which provide business services and persistence capabilities. To interact
with these components, clients must either locate the service component
(referred to as a lookup operation) or create a new component. For instance,
an EJB client must locate the enterprise bean's home object, which the
client then uses either to find an object or to create or remove one or
more enterprise beans. Similarly, a JMS client must first locate the JMS
Connection Factory to obtain a JMS Connection or a JMS Session.
All Java 2 Platform, Enterprise
Edition (J2EE) application clients use the JNDI common facility to look up
and create EJB and JMS components. The JNDI API enables clients to obtain
an initial context object that holds the component name to object bindings.
The client begins by obtaining the initial context for a bean's home object.
The initial context remains valid while the client session is valid. The
client provides the JNDI registered name for the required object to obtain
a reference to an administered object. In the context of an EJB application,
a typical administered object is an enterprise bean's home object. For
JMS applications, the administered object can be a JMS Connection Factory
(for a Topic or a Queue) or a JMS Destination (a Topic or a Queue).
So, locating a JNDI-administered service object is common to all clients
that need to access that service object. That being the case, it is easy
to see that many types of clients repeatedly use the JNDI service, and
the JNDI code appears multiple times across these clients. This results
in an unnecessary duplication of code in the clients that need to look
up services.
Also, creating a JNDI initial context object and performing a lookup
on an EJB home object utilizes significant resources. If multiple clients
repeatedly require the same bean home object, such duplicate effort can
negatively impact application performance.
Let us examine the lookup and creation process for various J2EE components.
-
The lookup and creation of enterprise
beans relies upon the following:
-
A correct setup of the JNDI environment
so that it connects to the naming and directory service used by
the application. Setup entails providing the location of the naming
service and the necessary authentication credentials to access
that service.
-
The JNDI service can then provide
the client with an initial context that acts as a placeholder
for the component name-to-object bindings. The client requests
this initial context to look up the EJBHome object for the required
enterprise bean by providing the JNDI name for that EJBHome object.
-
Find the EJBHome object using the
initial context's lookup mechanism.
-
After obtaining the EJBHome object,
create, remove, or find the enterprise bean, using the EJBHome
object's create, move, and find (for entity beans only).
-
The lookup and creation of JMS components
(Topic, Queue, QueueConnection, QueueSession, TopicConnection, TopicSession,
and so forth) involves the following steps. Note that in these steps,
Topic refers to the publish/subscribe messaging model and Queue refers
to the point-to-point messaging model.
-
Set up the JNDI environment to the
naming service used by the application. Setup entails providing
the location of the naming service and the necessary authentication
credentials to access that service.
-
Obtain the initial context for the
JMS service provider from the JNDI naming service.
-
Use the initial context to obtain
a Topic or a Queue by supplying the JNDI name for the topic or
the queue. Topic and Queue are JMSDestination objects.
-
Use the initial context to obtain
a TopicConnectionFactory or a QueueConnectionFactory by supplying
the JNDI name for the topic or queue connection factory.
-
Use the TopicConnectionFactory to
obtain a TopicConnection or QueueConnectionFactory to obtain a
QueueConnection.
-
Use the TopicConnection to obtain
a TopicSession or a QueueConnection to obtain a QueueSession.
-
Use the TopicSession to obtain a TopicSubscriber or a TopicPublisher
for the required Topic. Use the QueueSession to obtain a QueueReceiver
or a QueueSender for the required Queue.
The process to look up and create components involves a vendor-supplied
context factory implementation. This introduces vendor dependency in the
application clients that need to use the JNDI lookup facility to locate
the enterprise beans and JMS components, such as topics, queues, and connection
factory objects.
Forces
-
EJB clients need to use the JNDI API to
look up EJBHome objects by using the enterprise bean's registered
JNDI name.
-
JMS clients need to use JNDI API to look
up JMS components by using the JNDI names registered for JMS components,
such as connection factories, queues, and topics.
-
The context factory to use for the initial
JNDI context creation is provided by the service provider vendor and
is therefore vendor- dependent. The context factory is also dependent
on the type of object being looked up. The context for JMS is different
from the context for EJB, with different providers.
-
Lookup and creation of service components
could be complex and may be used repeatedly in multiple clients in
the application.
-
Initial context creation and service object
lookups, if frequently required, can be resource-intensive and may
impact application performance. This is especially true if the clients
and the services are located in different tiers.
-
EJB clients may need to reestablish connection to a previously accessed
enterprise bean instance, having only its Handle object.
Solution
Use a Service Locator object to abstract all JNDI usage and to
hide the complexities of initial context creation, EJB home object lookup,
and EJB object re-creation. Multiple clients can reuse the Service Locator
object to reduce code complexity, provide a single point of control, and
improve performance by providing a caching facility.
This pattern reduces the client complexity that results from the client's
dependency on and need to perform lookup and creation processes, which
are resource-intensive. To eliminate these problems, this pattern provides
a mechanism to abstract all dependencies and network details into the
Service Locator.
Structure
Figure 8.31 shows the class diagram representing the relationships for
the Service Locator pattern.
Figure 8.31 Service Locator class diagram
Participants and Responsibilities
Figure 8.32 contains the sequence diagram that shows the interaction
between the various participants of the Service Locator pattern.
Figure 8.32 Service Locator Sequence diagram
Client
This is the client of the Service Locator. The client is an object that
typically requires access to business objects such as a Business Delegate
(see "Business Delegate" on page 248).
Service Locator
The Service Locator abstracts the API lookup (naming) services, vendor
dependencies, lookup complexities, and business object creation, and provides
a simple interface to clients. This reduces the client's complexity. In
addition, the same client or other clients can reuse the Service Locator.
InitialContext
The InitialContext object is the start point in the lookup and creation
process. Service providers provide the context object, which varies depending
on the type of business object provided by the Service Locator's lookup
and creation service. A Service Locator that provides services for multiple
types of business objects (such as enterprise beans, JMS components, and
so forth) utilizes multiple types of context objects, each obtained from
a different provider (e.g., context provider for an EJB application server
may be different from the context provider for JMS service).
ServiceFactory
The ServiceFactory object represents an object that provides life cycle
management for the BusinessService objects. The ServiceFactory object
for enterprise beans is an EJBHome object. The ServiceFactory for JMS
components can be a JMS ConnectionFactory object, such as a TopicConnectionFactory
(for publish/subscribe messaging model) or a QueueConnectionFactory (for
point-to-point messaging model).
BusinessService
The BusinessService is a role that is fulfilled by the service the client
is seeking to access. The BusinessService object is created or looked
up or removed by the ServiceFactory. The BusinessService object in the
context of an EJB application is an enterprise bean. The BusinessService
object in the context of a JMS application can be a TopicConnection or
a QueueConnection. The TopicConnection and QueueConnection can then be
used to produce a JMSSession object, such as TopicSession or a QueueSession
respectively.
Strategies
EJB Service Locator Strategy
The Service Locator for enterprise bean components uses EJBHome object,
shown as BusinessHome in the role of the ServiceFactory. Once the EJBHome
object is obtained, it can be cached in the ServiceLocator for future
use to avoid another JNDI lookup when the client needs the home object
again. Depending on the implementation, the home object can be returned
to the client, which can then use it to look up, create, and remove enterprise
beans. Otherwise, the ServiceLocator can retain (cache) the home object
and gain the additional responsibility of proxying all client calls to
the home object. The class diagram for the EJB Service Locator strategy
is shown in Figure 8.33.
Figure 8.33 EJB Service Locator Strategy class diagram
The interaction between the participants in a Service Locator for an
enterprise bean is shown in Figure 8.34.
Figure 8.34 EJB Service Locator Strategy sequence diagram
JMS Queue Service Locator Strategy
This strategy is applicable to point-to-point messaging requirements.
The Service Locator for JMS components uses QueueConnectionFactory objects
in the role of the ServiceFactory. The QueueConnectionFactory is looked
up using its JNDI name. The QueueConnectionFactory can be cached by the
ServiceLocator for future use. This avoids repeated JNDI calls to look
it up when the client needs it again. The ServiceLocator may otherwise
hand over the QueueConnectionFactory to the client. The Client can then
use it to create a QueueConnection. A QueueConnection is necessary in
order to obtain a QueueSession or to create a Message, a QueueSender (to
send messages to the queue), or a QueueReceiver (to receive messages from
a queue). The class diagram for the JMS Queue Service Locator strategy
is shown in Figure 8.35. In this diagram, the Queue is a JMS Destination
object registered as a JNDI-administered object representing the queue.
The Queue object can be directly obtained from the context by looking
it up using its JNDI name.
Figure 8.35 JMS Queue Service Locator strategy class diagram
The interaction between the participants in a Service Locator for point-to-point
messaging using JMS Queues is shown in Figure 8.36.
Figure 8.36 JMS Queue Service Locator Strategy sequence diagram
JMS Topic Service Locator Strategy
This strategy is applicable to publish/subscribe messaging requirements.
The Service Locator for JMS components uses TopicConnectionFactory objects
in the role of the ServiceFactory. The TopicConnectionFactory is looked
up using its JNDI name. The TopicConnectionFactory can be cached by the
ServiceLocator for future use. This avoids repeated JNDI calls to look
it up when the client needs it again. The ServiceLocator may otherwise
hand over the TopicConnectionFactory to the client. The Client can then
use it to create a TopicConnection. A TopicConnection is necessary in
order to obtain a TopicSession or to create a Message, a TopicPublisher
(to publish messages to a topic), or a TopicSubscriber (to subscribe to
a topic). The class diagram for the JMS Topic Service Locator strategy
is shown in Figure 8.37. In this diagram, the Topic is a JMS Destination
object registered as a JNDI-administered object representing the topic.
The Topic object can be directly obtained from the context by looking
it up using its JNDI name.
Figure 8.37 JMS Topic Service Locator strategy
The interaction between the participants in a Service Locator for publish/subscribe
messaging using JMS Topics is shown in Figure 8.38.
Figure 8.38 JMS Topic Service Locator Strategy sequence diagram
Combined EJB and JMS Service Locator Strategy
These strategies for EJB and JMS can be used to provide separate Service
Locator implementations, since the clients for EJB and JMS may more likely
be mutually exclusive. However, if there is a need to combine these strategies,
it is possible to do so to provide the Service Locator for all objects-enterprise
beans and JMS components.
Type Checked Service Locator Strategy
The diagrams in Figures 8.37 and 8.38 provide lookup facilities by passing
in the service lookup name. For an enterprise bean lookup, the Service
Locator needs a class as a parameter to the PortableRemoteObject.narrow()
method. The Service Locator can provide a getHome() method,
which accepts as arguments the JNDI service name and the EJBHome class
object for the enterprise bean. Using this method of passing in JNDI service
names and EJBHome class objects can lead to client errors. Another approach
is to statically define the services in the ServiceLocator, and instead
of passing in string names, the client passes in a constant. Example 8.34
illustrates such a strategy.
This strategy has trade-offs. It reduces the flexibility of lookup, which
is in the Services Property Locator strategy, but add the type checking
of passing in a constant to the ServiceLocator.getHome()
method.
Service Locator Properties Strategy
This strategy helps to address the trade-offs of the type checking strategy.
This strategy suggests the use of property files and/or deployment descriptors
to specify the JNDI names and the EJBHome class name. For presentation-tier
clients, such properties can be specified in the presentation-tier deployment
descriptors or property files. When the presentation tier accesses the
business tier, it typically uses the Business Delegate pattern.
The Business Delegate interacts with the Service Locator to locate business
components. If the presentation tier loads the properties on initialization
and can provide a service to hand out the JNDI names and the EJB class
names for the required enterprise bean, then the Business Delegate could
request this service to obtain them. Once the Business Delegate has the
JNDI name and the EJBHome Class name, it can request the Service Locator
for the EJBHome by passing these properties as arguments.
The Service Locator can in turn use Class.forName(EJBHome ClassName)
to obtain the EJBHome Class object and go about its business of
looking up the EJBHome and using the Portable RemoteObject.narrow()
method to cast the object, as shown by the getHome() method
in the ServiceLocator sample code in Example 8.33. The only thing that
changes is where the JNDI name and the Class objects are coming from.
Thus, this strategy avoids hardcoded JNDI names in the code and provides
for flexibility of deployment. However, due to the lack of type checking,
there is scope for avoiding errors and mismatches in specifying the JNDI
names in different deployment descriptors.
Consequences
-
Abstracts Complexity
The Service Locator pattern encapsulates the complexity of this lookup
and creation process (described in the problem) and keeps it hidden
from the client. The client does not need to deal with the lookup
of component factory objects (EJBHome, QueueConnectionFactory, and
TopicConnectionFactory, among others) because the ServiceLocator is
delegated that responsibility.
-
Provides Uniform Service Access
to Clients
The Service Locator pattern abstracts all the complexities, as explained
previously. In doing so, it provides a very useful and precise interface
that all clients can use. The pattern interface ensures that all types
of clients in the application uniformly access business objects, in
terms of lookup and creation. This uniformity reduces development
and maintenance overhead.
-
Facilitates Adding New Business
Components
Because clients of enterprise beans are not aware of the EJBHome objects,
it's possible to add new EJBHome objects for enterprise beans developed
and deployed at a later time without impacting the clients. JMS clients
are not directly aware of the JMS connection factories, so new connection
factories can be added without impacting the clients.
-
Improves Network Performance
The clients are not involved in JNDI lookup and factory/home object
creation. Because the Service Locator performs this work, it can aggregate
the network calls required to look up and create business objects.
-
Improves Client Performance by Caching
The Service Locator can cache the initial context objects and references
to the factory objects (EJBHome, JMS connection factories) to eliminate
unnecessary JNDI activity that occurs when obtaining the initial context
and the other objects. This improves the application performance.
Sample Code
Implementing Service Locator Pattern
A sample implementation of the Service Locator pattern is shown in Example
8.33. An example for implementing the Type Checked Service Locator strategy
is listed in Example 8.34.
Example 8.33 Implementing Service Locator
package corepatterns.apps.psa.util;
import java.util.*;
import javax.naming.*;
import java.rmi.RemoteException;
import javax.ejb.*;
import javax.rmi.PortableRemoteObject;
import java.io.*;
public class ServiceLocator {
private static ServiceLocator me;
InitialContext context = null;
private ServiceLocator()
throws ServiceLocatorException {
try {
context = new InitialContext();
} catch(NamingException ne) {
throw new ServiceLocatorException(...);
}
}
// Returns the instance of ServiceLocator class
public static ServiceLocator getInstance()
throws ServiceLocatorException {
if (me == null) {
me = new ServiceLocator();
}
return me;
}
// Converts the serialized string into EJBHandle
// then to EJBObject.
public EJBObject getService(String id)
throws ServiceLocatorException {
if (id == null) {
throw new ServiceLocatorException(...);
}
try {
byte[] bytes = new String(id).getBytes();
InputStream io = new
ByteArrayInputStream(bytes);
ObjectInputStream os = new
ObjectInputStream(io);
javax.ejb.Handle handle =
(javax.ejb.Handle)os.readObject();
return handle.getEJBObject();
} catch(Exception ex) {
throw new ServiceLocatorException(...);
}
}
// Returns the String that represents the given
// EJBObject's handle in serialized format.
protected String getId(EJBObject session)
throws ServiceLocatorException {
try {
javax.ejb.Handle handle = session.getHandle();
ByteArrayOutputStream fo = new
ByteArrayOutputStream();
ObjectOutputStream so = new
ObjectOutputStream(fo);
so.writeObject(handle);
so.flush();
so.close();
return new String(fo.toByteArray());
} catch(RemoteException ex) {
throw new ServiceLocatorException(...);
} catch(IOException ex) {
throw new ServiceLocatorException(...);
}
return null;
}
// Returns the EJBHome object for requested service
// name. Throws ServiceLocatorException If Any Error
// occurs in lookup
public EJBHome getHome(String name, Class clazz)
throws ServiceLocatorException {
try {
Object objref = context.lookup(name);
EJBHome home = (EJBHome)
PortableRemoteObject.narrow(objref, clazz);
return home;
} catch(NamingException ex) {
throw new ServiceLocatorException(...);
}
}
} |
Implementing Type Checked Service Locator Strategy
Example 8.34 Implementing Type Checked Service Locator Strategy
package corepatterns.apps.psa.util;
// imports
...
public class ServiceLocator {
// singleton's private instance
private static ServiceLocator me;
static {
me = new ServiceLocator();
}
private ServiceLocator() {}
// returns the Service Locator instance
static public ServiceLocator getInstance() {
return me;
}
// Services Constants Inner Class - service objects
public class Services {
final public static int PROJECT = 0;
final public static int RESOURCE = 1;
}
// Project EJB related constants
final static Class PROJECT_CLASS = ProjectHome.class;
final static String PROJECT_NAME = "Project";
// Resource EJB related constants
final static Class RESOURCE_CLASS = ResourceHome.class;
final static String RESOURCE_NAME = "Resource";
// Returns the Class for the required service
static private Class getServiceClass(int service){
switch( service ) {
case Services.PROJECT:
return PROJECT_CLASS;
case Services.RESOURCE:
return RESOURCE_CLASS;
}
return null;
}
// returns the JNDI name for the required service
static private String getServiceName(int service){
switch( service ) {
case Services.PROJECT:
return PROJECT_NAME;
case Services.RESOURCE:
return RESOURCE_NAME;
}
return null;
}
/* gets the EJBHome for the given service using the
** JNDI name and the Class for the EJBHome
*/
public EJBHome getHome( int s )
throws ServiceLocatorException {
EJBHome home = null;
try {
Context initial = new InitialContext();
// Look up using the service name from
// defined constant
Object objref =
initial.lookup(getServiceName(s));
// Narrow using the EJBHome Class from
// defined constant
Object obj = PortableRemoteObject.narrow(
objref, getServiceClass(s));
home = (EJBHome)obj;
}
catch( NamingException ex ) {
throw new ServiceLocatorException(...);
}
catch( Exception ex ) {
throw new ServiceLocatorException(...);
}
return home;
}
} |
The client code to use the Service Locator for this strategy may look
like the code in Example 8.35.
Example 8.35 Client Code for Using the Service Locator
public class ServiceLocatorTester {
public static void main( String[] args ) {
ServiceLocator serviceLocator =
ServiceLocator.getInstance();
try {
ProjectHome projectHome = (ProjectHome)
serviceLocator.getHome(
ServiceLocator.Services.PROJECT );
}
catch( ServiceException ex ) {
// client handles exception
System.out.println( ex.getMessage( ));
}
}
} |
This strategy is about applying type checking to client lookup. It encapsulates
the static service values inside the ServiceLocator and creates an inner
class Services, which declares the service constants (PROJECT
and RESOURCE ). The Tester client gets an instance to the
ServiceLocator singleton and calls getHome() , passing in
the PROJECT . ServiceLocator in turn gets the JNDI entry name
and the Home class and returns the EJBHome.
Related Patterns
-
Business Delegate
The Business Delegate pattern uses Service Locator to gain access
to the business service objects such as EJB objects, JMS topics, and
JMS queues. This separates the complexity of service location from
the Business Delegate, leading to loose coupling and increased manageability.
-
Session Facade
The Session Facade pattern uses Service Locator to gain access to
the enterprise beans that are involved in a workflow. The Session
Facade could directly use the Service Locator or delegate the work
to a Business Delegate (See "Business Delegate" on page
248.).
-
Transfer Object Assembler
The Transfer Object Assembler pattern uses Service Locator to gain
access to the various enterprise beans it needs to access to build
its composite Transfer Object. The Transfer Object Assembler could
directly use the Service Locator or delegate the work to a Business
Delegate (See "Business Delegate" on page 248.).
Contact Us© 2001-2002 Sun Microsystems,
Inc. All Rights Reserved.
|