The software development profession has moved towards making the assembly of an application independent from the development of the applications components. Though this component-based approach has proven unworkable in many situations, in other cases, a component-based design leads to a lower-cost and higher-speed application. If the design of the original component is sufficiently flexible, you can even combine this component with new components in ways you didnt anticipate when you developed the original component. (This quest for flexibility can also become a burden. Sometimes it is impossible to have sufficient flexibility and a reasonable interface without the component becoming so heavyweight that it is unusable.)
Achieving immutable source code is an important step in the evolution of software development. To meet this requirement, component architectures such as EJB (Enterprise JavaBeans) must provide the means to dynamically bind components. Dynamic binding is the deferral until deployment or even run time of a client objects association with a specific enterprise bean. With dynamic binding, a developer implements a client class that interacts with an enterprise bean through a particular interface. Any enterprise bean that implements this interface may be associated with the client at run time without requiring the alteration of the clients source code.
The EJB architecture contains a framework for binding enterprise beans statically (at compile time). However, this framework provides no obvious means for performing dynamic binding.
Last year, I designed and implemented a JMS (Java Message Service) framework that I used numerous times in several different systems. For each instance of this framework, I had to couple the message-driven bean in the framework to a session bean that could process the set of message types. Since this session bean would be developed long after the JMS framework, I needed some method to dynamically bind the message-driven bean to the session bean. This article describes the resulting method for dynamically binding beans using Javas Reflection API.
Static Binding
The following discussion presumes some familiarity with EJB. See the sidebar for information on implementing and deploying a session bean.
In the conventional approach, the EJB Specification stipulates that every session bean must expose at least two interfaces to its clients. The first interface is a proxy interface that extends either the javax.ejb.EJBObject or javax.ejb.EJBLocalObject interface. The second interface is a home interface that extends either the javax.ejb.EJBHome or javax.ejb.EJBLocalHome interface [4]. The exposed home interface must contain one or more create methods that return a reference to an instance of the beans proxy interface. The proxy interface contains the set of methods that the bean exposes to its clients. The EJB specification further requires that clients use the javax.rmi.PortableRemoteObject class to cast from the generic java.lang.Object reference returned by the namespace to the specific home interface reference needed to interact with the desired enterprise bean [5].
Imagine a session bean named StaticLiaisonBean that relies on an UpperCaseConverterBean to help it accomplish some unit of work. At run time, the client StaticLiaisonBean needs to look up the home interface for UpperCaseConverterBean in the EJB containers namespace, resolve this generic reference to a usable home-interface reference, and invoke the desired create method exposed by that home interface to acquire a reference to a proxy object for an instance of UpperCaseConverterBean (see Listing 1).
The StaticLiaisonBean class knows, at compile time, everything it needs to know about the session bean it plans to work with:
- The JNDI name that maps to the home interface (string "ejb/TargetBean" in Listing 1, line B).
- The actual home interface name and its package (UpperCaseConverterHome in Listing 1, line C and its corresponding import statement).
- The proxy interface name and its package (UpperCaseConverterRemoteProxy in Listing 1, line A and its corresponding import statement).
Thus, StaticLiaisonBean is statically bound to UpperCaseConverterBean. To reuse the logic contained in StaticLiaisonBean, but have it interact with a LowerCaseConverterBean, all three of these lines (and supporting import statements) need to be modified and the bean recompiled.
Dynamic Binding
To permit dynamic binding, one must implement StaticLiaisonBean such that it does not require recompilation when associated with a session bean other than UpperCaseConverterBean. The problem can be broken into a set of intertwined challenges:
- Abstract away from a specific JNDI name that maps to an object that implements an arbitrary session beans home interface.
- Provide the javax.rmi.PortableRemoteObject.narrow method with a java.lang.Class object that represents the home interface for an arbitrary session bean.
- Devise a way to acquire a reference to the home of an arbitrary session bean and create a reference to the remote proxy.
- Devise a way to declare a variable to hold a reference to the remote proxy of an arbitrary session bean in order to call the session beans methods.
To address the first challenge, I added a layer of indirection by introducing a new environment entry to the liaison beans deployment descriptor to store the JNDI name string for the desired target beans home interface.
<env-entry> <env-entry-name>targetJNDI</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>ejb/TargetBean</env-entry-value> </env-entry>
The liaison beans source code still has a hard-coded JNDI name string (Listing 7, line F). However, instead of referencing the target beans home interface, the liaison bean now references a second JNDI name string. This second JNDI name string references the target beans home interface (Listing 7, line G). Since this second JNDI name string resides in the deployment descriptor, it is easily configurable until run time. Thus, I have deferred until application assembly the need to specify the JNDI name this bean should use to locate its associate.
I applied this same approach along with Reflection [6] to solve the second challenge. I add another environment entry to the liaison beans deployment descriptor to store the fully specified name for the target beans home interface.
<env-entry> <env-entry-name>targetHomeClass</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value> ...converter.LowerCaseConverterHome </env-entry-value> </env-entry>
At run time, the liaison bean fetches this home-interface name (Listing 7, line H) and passes it as an argument to the reflective static method java.lang.Class.forName( String converterHomeInterface ) to acquire an instance of the home interfaces associated java.lang.Class (Listing 7, line I). The liaison bean also acquires the generic java.lang.Object reference to the desired target beans home interface (Listing 7, line J). The liaison bean ensures it acquired what it expected by passing the home-interface reference and the java.lang.Class instance to the static method javax.rmi.PortableRemoteObject.narrow in order to safely downcast the reference to the target beans home-interface type (Listing 7, line K).
I also made use of Reflection to solve the third challenge. My approach assumes the set of interchangeable beans can all employ a common set of arguments for the create methods in their home interfaces. (Given the objective of immutable source code, I cannot modify the client bean to accommodate new arguments.) For my purposes, I am content to use a create method with no arguments. I use the java.lang.Class instance of the target beans home interface (acquired while solving the second challenge) and invoke its getMethod( String name, Class[] parameterTypes ) method to acquire a java.lang.reflect.Method instance for this create method (Listing 7, line L). I then call the invoke method of this java.lang.reflect.Method instance, passing in the home-interface instance, and cast the returned remote proxy reference to the ConverterInterface common interface (Listing 7, line M).
The common interface design strategy described by Richard Monson-Haefel [7] provides part of the solution to the fourth challenge. The motivation for this design strategy is the desire to ensure that the set of business methods exposed by the enterprise bean is consistent with the business methods declared by the enterprise beans remote interface [8]. To apply the strategy, one collects the set of business methods exposed by the enterprise bean in an interface (Listing 8). One then extends this common interface with the proxy interface (Listing 9) and implements the interface via the bean class (Listing 7, line D). Since the remote interface is tied to the bean class by this common interface, the Java compiler can ensure their consistency, and anyone reviewing the code will immediately discern the relationship.
I can take advantage of this design strategy in another way. The common interface provides a data type you can use to declare a variable that holds a reference to an arbitrary remote proxy. I need only use a single common interface for all of the beans that collaborate with the liaison bean. I can then provide a reference to this common interface in the liaison beans source code and interact with the different session beans through this common interface (Listing 7, line E).
Using the common interface, ConverterInterface, I add a sibling bean to UpperConverterBean called LowerConverterBean. To demonstrate dynamic binding, I assembled a liaison component called DynamicLiaison (Listing 10, bold stanzas). This component contains two beans, both instances of DynamicLiaisonBean. One dynamically binds to UpperConverterBean, while the other binds dynamically to LowerConverterBean. If you download the application that complements this article (<www.cuj.com/java/code.htm>, you may exercise DynamicLiaisonClient and see this component in action.
Conclusion
The solution I have described is a useful technique that belongs in every EJB developers toolbox. However, you should use this technique judiciously. Although Reflection offers a powerful means to loosely couple object associations, this power comes with a price. Reflection will degrade an applications performance if you deploy it carelessly. This technique can also obscure your view of an applications design, which can complicate knowledge transfer and troubleshooting.
As I wrote this article, I found two other reports [9, 10] from EJB developers who independently encountered problems requiring the application of dynamic binding. Each of these developers came up with a solution that relied on the Java Reflection API. I would be interested in hearing from any developers who have used other strategies.
Notes and References
[1] Richard Monson-Haefel. Enterprise JavaBeans (OReilly & Associates, Inc., 2001).
[2] If you elect to use a J2EE application server other than the J2EE reference implementation, you will need to modify and possibly extend the component and application deployment descriptors I have provided to conform to your vendors augmentation of the specification.
[3] Richard Monson-Haefel describes this bean adaptor design strategy in [1] (see chapter 15).
[4] Prior to the EJB 2.0 specification, interaction between client objects, session beans, and entity beans could only occur through a component interface that derived from java.rmi.Remote. In using this component interface, enterprise beans residing in the same EJB container needlessly incurred the overhead associated with RMI. The EJB 2.0 specification addressed this issue by defining a local component interface that is not based on a distributed object communication protocol.
[5] This is because the EJB Specification employs Java RMI-IIOP, which requires compliance with the IIOP 1.2 protocol.o
[6] I presume that the reader is familiar with the Java Reflection API. If an overview is desired, I would suggest section 11.2 of Ken Arnolds book [11]. A quick search of the Web will also yield a number of useful articles. I have suggested a few in the references too.
[7] Richard Monson-Haefel describes this common interface design strategy in [1] (see chapter 15).
[8] The EJB architecture does not link a bean class and its associated remote interface through a common ancestor. Rather, it relies on the automated deployment tool associated with an EJB container to enforce consistency between the two.
[9] Sriram Chakravarthy. Bringing Together the Power of Application Servers and JMS Messaging, <www.theserverside.com/resources/article.jsp?l=Bringing-Together-Application-Servers-and-JMS-Messaging>.
[10] Gorsen Huang. Java Tip 118: Utilize the EjbProxy, Java World, <www.javaworld.com/javaworld/javatips/jw-javatip118.html>.
[11] Ken Arnold, James Gosling, and David Holmes. The Java Programming Language, Third Edition (Addison-Wesley, 2000).
[12] Linda G. DeMichiel, L.Yalcinalp, and Sanjeev Krishnan. EJB Specification, Version 2.0 (Sun Microsystems, 2001).
Douglas A. Clark is a consulting software developer working in the Kansas City area. He holds a masters degree in Computer Science and has more than eight years experience designing and implementing distributed systems. His areas of interest include component-based software and transaction-oriented systems. He can be reached at [email protected].