|
22.2. EJBs: You Don't Know Beans?Enough theory about EJBs and naming services and the like. It's time to put together an actual EJB so you can see one run. First we need to write our EJB classes, then compile them. Then, in the next chapter, we'll package them, along with other supporting information, into an EAR file. But let's begin by writing some Java classes. It's not that we just write a single EJB class, say a session bean, and we're done. Keep in mind that we're going to be using these beans in a distributed environment, so we need a way to have an application running on one system find, create, look up, or otherwise access the bean running on another machine somewhere in our enterprise network. The job of EJBs is to simplify (up to a point) the efforts of the application programmer doing all this, and make it seem as if the bean is quite local or at least independent of location. Here's how it works. Any application that wants to use the functions provided by an EJB must first locate the bean. It uses a naming service (Chapter 21) for this. What it gets from the lookup is something called a home interface. The home interface object is in effect a factory for producing remote interfaces, which are the proxies for the actual service(s) that our application wants to use. A remote interface has the method signatures that give the functionality that the application is after, but it doesn't do the actual work of the bean. Rather, it is a proxy for the bean. The remote interface's job is to do all the work behind the scenes to marshal the arguments and send them off to the bean, and to unmarshal the results returned from the bean. So it's a three step process:
What's all this talk about interfaces? They provide a way to define the methods you want to use, but without having to write all the code to do it. For example, with the remote interface you may define a method to do something with several arguments, say Blah (a, b, c). Now the remote object doesn't really do the Blah work; its job is to marshal the arguments (serialized a, b, and c) and send them off to the EJB to do whatever Blah is, and then unmarshal the results. So you as an application programmer will write the guts of Blah in the EJB object, but for the remote object, its proxy, you only need to declare the method signature. Then the job of the EJB container (e.g., JBoss or Geronimo) is to provide the smarts of the proxythat is, to generate a Java class that implements your interface, along with the code that knows how to contact your EJB and marshal and unmarshal the arguments and results. That's right, the EJB container (server) makes code that uses your interfaces, along with its own code, to do the infrastructure work of EJBs. Talking about all these pieces of an EJB can be confusing, too. Sometimes it is helpful to think of an EJB as a single class; sometimes it's better to think of it as a family of classes that act together pretending to be a single bean that is distributed across several hosts. This can make it a bit confusing when talking about an EJBdo we mean the family of interacting classes or do we mean the single class that provides the application functionality that we want? The names of EJB classes and EJB interfaces (which we will extend and implement) don't help much eitherthey can be confusing, too. For example, we will extend EJBObject, but not to write an EJB session bean; no, we extend SessionBean for that, but EJBObject is the name for the remote interface. Go figure. A bit of perspective may help here. The names Remote, Local, and Home are used as modifiers on these classes. Local means "on the same host as the bean." But Home and Remote don't offer much of a clue. The home interface is what we get from a lookup; it produces remote objects (objects which implement the remote interface). A remote object is what our application uses as if it were a Java object doing what we need, even though its application-specific activity will happen on a bean somewhere else on the network. Let's look at a very very simple example, to see the pieces in action. 22.2.1. SessionBeanLet's write a stateless session bean that will compute the time value of money. Why that? Well, two reasons. First, we already have an SAMoney class with a save() method for computing some values; and second, we need some simple, stateless, but somewhat computationally intensive task to make for a halfway reasonable example. The real guts of an EJB, the core of the application functionalityin our example, the computation of the time value of moneyis the session (or entity) bean. For our session bean we begin by implementing the SessionBean interface, which means that we need to define these methods: public void setSessionContext(SessionContext context) { } public void ejbCreate() { } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } which, for our example, we can implement as empty methods. A stateless session bean never needs an activation or passivation method to do anythingit is pointless to passivate a stateless session bean. Why? Since it's stateless, any instance of it is as good as any other, so the instances are interchangeable and there's no need to passivate one to get to anotherjust use the one available. It follows that if a bean is never passivated, it will never have to be activated. But why no body to the ejbCreate() method? Well, our bean isn't doing anything extra. This would only be used if our example were more complicated and we needed to do application-specific initializations. For example, if the bean had to connect to a database (and did not use entity beans), it might establish the JDBC connection in ejbCreate and close it in ejbRemove(). Similarly, we can have an empty ejbRemove() method. Next we add our own methods, the ones that provide the application functionality. For our MoneyBean application, we'll add save() and debt() methods which will use an SAMoney class by calling its save() and debt() methods. Example 22.1 is the listing of the SessionBean. Example 22.1. Listing of our implementation of a SessionBeanpackage com.jadol.budgetpro; import net.multitool.util.*; import javax.ejb.*; /** * Actual implementation of the EJB */ public class MoneyEJBean implements SessionBean { protected SessionContext sessionContext; // typical; just not used now public Cost save(double amt, double rate, double paymnt) throws java.rmi.RemoteException { return SAMoney.save(amt, rate, paymnt); } // save public Cost debt(double amt, double rate, double paymnt) throws java.rmi.RemoteException { return SAMoney.debt(amt, rate, paymnt); } // debt public void setSessionContext(SessionContext context) { sessionContext = context; } // setSessionContext public void ejbCreate() { } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } } // interface MoneyEJBean 22.2.2. EJBObjectAt the other end of the chain of EJB objects used to accomplish all this distributed computing is the object that our application is actually going to touch. When our application creates an EJB, it, acting as a client, won't actually get its hands on the distant session (or entity) bean because that session bean is running somewhere out in the network. There will be, however, a proxy object, acting on behalf of the distant EJB. It is described as the remote interface, because it is remote from the EJB (though very close to the application). It is an interface because J2EE supplies a class that does the hidden work of marshaling the data, contacting the EJB, sending the data and receiving the results; the application developer only adds a few additional application-specific methods, via this interface (Example 22.2). Example 22.2. Sample remote interfacepackage com.jadol.budgetpro; import javax.ejb.*; import java.rmi.*; import net.multitool.util.*; /** * Remote Interface for the Money EJB */ public interface Money extends EJBObject { // the methods from the remote object which we will call public Cost save(double amt, double rate, double paymnt) throws java.rmi.RemoteException; public Cost debt(double amt, double rate, double paymnt) throws java.rmi.RemoteException; } // interface Money The crucial thing to note with this interface is that we have defined two methods that match the two methods in our SessionBeanthe save() and debt() methods. These are the methods that will actually be called by our application, and the J2EE mechanisms will do their work behind the scenes to connect to the methods of our SessionBean implementation and send back the results. 22.2.3. EJBHomeBetween the SessionBean and its remote interface lies the home interface, also called the remote home interface, since it pairs with the remote interface. An object that implements the home interface is the kind of object that is returned after the lookup() and then narrow() method calls. It is used to create a reference to the EJB. The home interface for a stateless session bean needs only implement a single method, the create() method with no arguments. The body of the method needs do nothing. All the real work is done by the underlying object supplied by J2EE. Example 22.3 is a listing of our home interface. It looks like an empty shell, but it is all that we need. The rest is handled by J2EE. Example 22.3. Sample (remote) home interfacepackage com.jadol.budgetpro; import javax.ejb.*; import java.rmi.*; /** * Remote Home Interface */ public interface MoneyHome extends EJBHome { public Money create() throws CreateException, RemoteException ; } // interface MoneyHome 22.2.4. Summarizing the PiecesWith these three piecesthe session bean, the remote interface, and the home interfacewe can see the structure of the key pieces of an EJB. Let's review what we have:
22.2.5. EJBLocalHome and EJBLocalObjectWhen the session or entity bean is going to be referenced by application code that resides on the same host as the bean, there are variations on the home and remote interfaces that allow for more efficient execution. When you know that the beans are local to this host, you should use a local interface (Example 22.4) and a local home interface (Example 22.5). The local interface is in place of the remote interface and extends EJBLocalObject. The local home interface is in place of the remote home interface and extends EJBLocalHome. Example 22.4. Sample local interfacepackage com.jadol.budgetpro; import javax.ejb.*; import java.rmi.*; /** * Local Interface for the Money EJB */ public interface MoneyLocal extends EJBLocalObject { // the methods which we will call } // interface MoneyLocal Example 22.5. Sample local home interfacepackage com.jadol.budgetpro; import javax.ejb.*; import java.rmi.*; /** * Local Home Interface */ public interface MoneyLocalHome extends EJBLocalHome { public MoneyLocal create() throws CreateException; } // interface MoneyLocalHome Why bother? Well, there's no need to marshal and unmarshal all that data if the calls are staying on the same host. This saves execution time. Perhaps more importantly, since the arguments don't have to be marshaled and unmarshaled, they don't have to be serializable. For some applications, this is the only way that they can use beans. Finally, keep in mind that the choice of local versus remote interfaces is not necessarily an either-or choice. For our session bean we have defined both kinds of interfaces. Then the deployment can determine which one will be used. 22.2.6. Compiling Your BeansIn order to compile these bean-related Java classes, you need to have a J2EE JAR in your classpath. If you've installed JBoss into /usr/local/jboss, you could add the JAR to your classpath this way: export CLASSPATH="/usr/local/jboss/client"\ "/jboss-j2ee.jar:.:${CLASSPATH}" If you have the Sun J2EE reference implementation installed on your system (in /usr/local), then you could use: export CLASSPATH="/usr/local/SUNWappserver"\ "/lib/j2ee.jar:.:${CLASSPATH}" If you have Geronimo installed on your system (with an environment variable GHOME to hold its location), then you would use: [1]
export CLASSPATH="${GHOME}/repository/geronimo-spec/jars"\ "/geronimo-spec-j2ee-1.0-M1.jar:.:${CLASSPATH}" In any case, the point is to have in your classpath the JAR file which contains the javax/ejb/ classes, such as EJBObject.class. If you haven't installed one of these containers on your machine, then download a copy of the JAR from the machine where that container is installed. Put your copy somewhere in your classpath, as in the examples above. If you are using Ant (and why wouldn't you be?) you will need to put the path to the J2EE JAR in the classpath which Ant uses. Since Ant can define its own classpath, you may want to define your compile task as something like this: <target name="compile" > <javac srcdir="${src}" destdir="${build}" classpath="/usr/local/SUNWappserver/lib/j2ee.jar:${stdpath}" /> </target> Now it's a simple matter to compile. The basic Java compile command: $ javac com/jadol/budgetpro/*.java will compile all the various classes and interfaces that make up the EJB. There may be other classes in your source tree that need to be compiled as well. With all that going on, you can see why so many people use Ant. With the compile target defined as in our example above, you would need only the command: $ ant compile |
|