|
SYS-CON.TV Webcasts
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Top Links You Must Click On
General Java Making Your EJBs Polymorphic
Making Your EJBs Polymorphic
By: John Musser
Aug. 1, 2002 12:00 AM
Inheritance and polymorphism are two of the most fundamental concepts in the object-oriented design world. They are used extensively in all Java applications, except J2EE apps using EJBs. Oh sure, developers implement various bean, remote, and home interfaces but often that's it. No significant class hierarchies. No polymorphic beans. Occasional reuse. Why is that? In this article we'll first review why this is the norm in EJB-based applications and then demonstrate techniques you can use to reclaim some of these goals within the constraints imposed by the EJB standards. (We are assuming you're familiar with the basics of EJBs.) The crux of this article focuses on an application that takes the classic object-oriented design example of a hierarchy of vehicle types and translates it into polymorphic EJBs. The aim is to combine some advantages of inheritance such as code reuse and polymorphism with the advantages of EJBs such as distributed transactional components. While this is not a universally applicable technique, you should by the end see how there's a suitable subset of problems that can be aptly solved with this approach. We'll also review interesting Java Naming and Directory Interfaces (JNDI) and other coding techniques you may find useful. A complete set of working examples (EJBs, client, descriptors, and scripts) are available on the JDJ Web site (www.sys-con.com/java/sourcec.cfm) and have been tested under WebLogic 6.1 and JBoss 2.4.
Implementation Inheritance and Polymorphism
To achieve this behavioral characteristic there are two classic approaches: inheritance or interfaces. In the inheritance model there would be a base Shape class, likely defined as abstract, from which a set of subtypes would be derived. The base defines a set of methods and attributes and probably implements some shared behavior used by all subclasses. This implementation inheritance is typically an "is-a" approach: a circle is a shape. In the interface model an interface such as shape would be defined, and any class that wants to "act-as-a" shape would implement the methods in this interface (although for "shapes" the typical design is "is-a"). This interface would define one or more methods that any implementer is required to define. Also note that interfaces themselves can form hierarchies and thereby achieve interface inheritance. Again, you see this throughout the various JDKs and your own code. The key tradeoff between these approaches is often seen through a design model that differentiates between these "is-a" and "acts-as-a" behaviors, as well as the fact that Java supports only single inheritance and interfaces provide a mechanism that allows objects to fulfill multiple roles. One of the benefits of implementation inheritance is code reuse in which common code exists or is refactored into base classes and shared by all derived types. In interface inheritance this is possible only through other design techniques such as containment and delegation. (And it's fair to say that there isn't always a right or wrong approach to choose a healthy ongoing debate exists as to the design tradeoffs between the implementation versus interface inheritance.) In EJBs, much of what occurs along these lines uses the interface-based approach. Many of the key APIs in the javax.ejb package are interfaces that your classes must define: EJBObject, EJBHome, SessionBean, EntityBean, etc. Within most EJBs the interactions with other objects typically occur through containment, delegation, and other "uses" relationships rather than through inheritance. This is reflected in most of the common EJB design patterns: session facade, value objects, and business delegates. Let's look into why.
Vehicle Hierarchy Example
In the Vehicle base class we want to define a set of methods and attributes that all vehicles will support. Subsequently, at runtime we want to be able to treat instances of the vehicle hierarchy equally so we can invoke the startEngine() without concern for whether the exact subtype being used is a car, truck, or other derived type. In addition, the base class will implement some of these methods so they need not be redefined for each subtype, and some common attributes, such as fuelLevel, will be defined in the base class as well. This is all fairly basic. In the world of EJB this isn't nearly so simple. With EJBs a component is not one object, it's three: the bean, its remote interface, and its home factory. It's these three parts that cause nearly all the complications. It's only by virtue of the Java language that each of these parts may be base or derived types, but the EJB as a whole is not "inheritable." (As a matter of fact, the final EJB 2.0 specification in Appendix D.4 states, "The current EJB specification does not specify the concept of component inheritance. There are complex issues that would have to be addressed in order to define component inheritance." But it does go on to say, "however, the bean provider can take advantage of the Java language support for inheritance" using interface inheritance and implementation inheritance both of which we do here.) Figure 2 shows the bare-bones vehicle hierarchy and the core EJB design that we'll discuss later. See how quickly the boxes multiply. Yet from an EJB perspective this is quite a simple picture. On the client side (for the home and remote interfaces) there's no extra inheritance here; it's just the bare minimum that's required for any EJB. On the bean side there's just a small hierarchy of those two derived types inheriting from a base (this time VehicleEJB), which in turn implements the standard SessionBean interface. We'll continue with our example in more detail later, but it's instructive to first look at how our simple pieces in Figure 2 fit into the final bigger picture (see Figure 3). As you can see, this is more complex still. What this figure demonstrates is the greater whole into which all EJBs fit. At the top are the base interfaces, such as java.rmi.Remote and java.io.Serializable, while at the bottom are the actual container-generated final implementation classes. (Note that each server is free to define and construct these actual implementations as it sees fit for example, JBoss dynamically constructs implementations whereas WebLogic builds these when the EJBC compiler is run.) Because of the potential complications in utilizing inheritance with EJBs, we've chosen a relatively simple design pattern for our approach. The key is that we perform inheritance only on the bean implementation side, expose a common remote interface, and use JNDI lookups to grab the appropriate binding at runtime. To focus on polymorphism and object behavior, our design centers on stateful session beans (and is equally applicable to stateless session beans). With entity beans there are additional complications, such as returning differing primary keys, which we'll discuss in more detail later. As for message-driven beans, because they have no homes or remotes and a single asynchronous onMessage() point of entry, our model doesn't really apply. (As a side note, IBM, in their VisualAge for Java product, provides extensions for EJB inheritance, but because these are proprietary we don't cover them here.)
The Code
To connect to the JNDI tree, a context has to be created with an optional Hashtable as a parameter. The Hashtable values specify the provider's JNDI context factory as well as the URL to the JNDI tree. The code below shows how this is retrieved for JBoss (note that WebLogic's JNDI connection factory is a class called "weblogic.jndi.WLInitialContextFactory" and the default URL is "t3://localhost:7001"). Hashtable env = new Hashtable();Leveraging JNDI With that context in hand, the client can call its own buyVehicle() method to get a VehicleRemote based on the command-line options. It's here that things get a little interesting. First of all, the EJB specification requires a session bean to define an EJBHome object that contains factory-like create methods to construct the bean. In our case, this is a simple two-argument create() that takes the vehicle color and an air-conditioning flag as arguments and returns a VehicleRemote reference. The remote interface contains our shared "business" methods: startEngine(), accelerate(), brake(), and getFuelLevel(). All vehicle implementations use the same home and remote interface. You might now ask: How can I access a specific subtype vehicle implementation on a J2EE server when the home and remote interfaces are the same for all vehicles? This happens with the magic of JNDI. Within an EJB container, and a J2EE server more generally, the required JNDI services provide a mechanism to store object instances within a globally accessible namespace (a JNDI tree). These objects can then be remotely retrieved using the JNDI name as a key. In addition, the EJB specs require application servers to publish deployed EJB home interfaces using JNDI names as defined in each EJB's deployment descriptor. This means we can use JNDI as a level of indirection. First, each concrete vehicle implementation gets bound to the JNDI tree under its own unique JNDI name (to be more accurate, it's the home interface that's looked up by name). This VehicleHome object is then used to create a VehicleRemote reference that in turn instantiates and manages (with the assistance of the container) the designated EJB instance. It's this association between a home/remote pair and an EJB class, which is made in the deployment descriptor, that allows us to use the same home and remote interfaces but create different subtypes on the server. If this sounds a bit confusing, the next section may help clarify things. The mechanism for this binding is specified in the following two deployment descriptors: the generic ejb-jar.xml used to describe the session beans (see Figure 4) and the vendor-specific decriptor used to assign a JNDI name to each EJB jboss.xml or weblogic-ejb-jar.xml for JBoss and WebLogic, respectively (see Figure 5). (Note that in this example we've chosen to assign the beans JNDI names that are the same as their EJB names in these descriptor files this is not required, we could have chosen differing JNDI names without breaking the rules of J2EE or our polymorphism.) In our example, the result is that we can use the exact same code on the client side, but at runtime can construct and interact with different EJB subtypes by simply changing the JNDI name we look up. Here's an example of the code that does this using the context we obtained earlier. First a VehicleHome is obtained from JNDI based on the binding name specified: VehicleHome vehicleHome = (VehicleHome) context.lookup("Car"); Now we can create a specific vehicle (a CarEJB) using the home interface's create method that takes two arguments, a color and an air-conditioning boolean flag: VehicleRemote vehicle = vehicleHome.create("silver", false); Finally we get to drive: we start the vehicle, get up to speed, slow down for a curve, and then turn: vehicle.startEngine();This block of code can work equally well if its remote reference is to a CarEJB or a TruckEJB. Figure 6 shows a similar example in which a client first does a JNDI lookup on a "Car", gets the home, creates the remote, and then drives the car. It then does a lookup on "Truck" and can perform the identical steps.
Issues and Alternatives
For example, it might seem that creating a hierarchy of home interfaces that parallels the beans would be nice. But the create() method of an EJBHome poses an interesting problem. In particular, the Java language does not allow overloading method signatures based on return type. Why does this matter? Because it means you can't do this: public BaseHome extends EJBHome {In addition, the ejbCreate() method of bean-managed entity beans must return a primary key and derived classes must return that same type. This is true for single object finder methods as well. With finders, whether returning single objects or Collections, it would be nice if, for a hierarchy of EJBs, the base class finder could return the right subtype(s) for the given search criteria, but unfortunately this can't happen automatically with the current standard. Which in the end is why home interface inheritance is particularly problematic and why our examples avoid it too messy. Not to say that there aren't other EJB inheritance alternatives to consider. A more elaborate approach is to use interface inheritance on the client side, where each derived type on the bean side gets a corresponding derived remote interface on the client side (see Figure 7). The advantage to this approach is that it allows the client to access methods defined only in the derived types, not just those in the base Vehicle class. For example, if TruckEJB adds an unhookTrailer() method, that can be added to the TruckRemote interface. The disadvantage is that the client, at some point in the code, must now explicitly differentiate between derived types. It can no longer just pass in a different JNDI name and get a VehicleHome; it must create distinct home types (remember, we can't have a single home create different remote types). Still, at most points the client code can benefit by interacting with the more general VehicleRemote objects anywhere specific subtype methods are not needed. (Keep in mind that in our primary example, the base VehicleEJB also adds a bit of value by fulfilling the role of an adapter pattern by providing default implementations of the EJB life-cycle methods such as ejbRemove(), ejbPassivate(), and setSessionContext() in which we cache the context in a protected member variable.) Another technique, one that isn't so much an alternative as it is a minor modification to the first design, is to define a business interface called Vehicle and have both VehicleRemote and VehicleEJB implement this interface (see Figure 8). This provides a degree of consistency and explicit signature enforcement across both sides. In the end, if and how you choose to use polymorphism and inheritance with your EJBs will naturally vary according to your needs. Over time, the EJB specifications will inevitably evolve and the ways in which this can be done will evolve along with them. Reader Feedback: Page 1 of 1
Your Feedback
Enterprise Open Source Magazine Latest Stories . . .
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||