Beginning EJB programming using jBoss
Step 1: creating the Bean
In this step we will write and compile a simple Enterprise JavaBean.
The example -- which is called `Interest' -- is about as simple as an EJB can get: it is a `stateless session bean'. Its job is to calculate the amount of compound interest payable on a sum of money borrowed over a specified term with a specified interest rate. In fact, there is only one functional line of code in the whole package.
You can copy/paste this code and the commands into your system, or you can download the attachment, jBossSimpleExample.zip, at the end of this Wiki page. That zip file contains all of the source code and Windows .bat files for executing the commands. If you installed JBoss in a non-standard location, you'll need to customize environment variable JBOSS_LOC in file set_jboss_loc.bat to represent the location of JBoss on your system.
-
EJBs: review
As a reminder, and Enterprise JavaBean has a minimum of three classes.
The remote interface. This is the class that exposes the methods of the Bean to the outside world. In the example, the remote interface is the class com.web_tomorrow.interest.Interest
The Bean class. This implements the methods specified by the remote interface. In this example, the Bean class is com.web_tomorrow.interest.InterestBean
The home interface. This specifies how a new Bean is created, managed and deleted. As a minimum it should specify at least one create() method. There should be an ejbCreate() method in the Bean class for each create() method in the home interface. In this example, the home interface is com.web_tomorrow.InterestHome
Of course, a Bean can include other classes, or even other packages, but the classes listed above are the minimum.
-
Coding the classes
We need three classes: the remote interface, the Bean, and the home interface.
The remote interface in this example is very simple.
Interest.java: remote interface for the `interest' EJB
-
package com.web_tomorrow.interest; import javax.ejb.EJBObject; import java.rmi.RemoteException; /** This interface defines the `Remote' interface for the `Interest' EJB. Its single method is the only method exposed to the outside world. The class InterestBean implements the method. */ public interface Interest extends EJBObject { /** Calulates the compound interest on the sum `principle', with interest rate per period `rate' over `periods' time periods. This method also prints a message to standard output; this is picked up by the EJB server and logged. In this way we can demonstrate that the method is actually being executed on the server, rather than the client. */ public double calculateCompoundInterest(double principle, double rate, double periods) throws RemoteException; }
-
The remote interface specifies only one "business method" calculateCompoundInterest.
The home interface is even simpler.
Interest.java: remote interface for the `interest' EJB
-
package com.web_tomorrow.interest; import java.io.Serializable; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; /** This interface defines the "home" interface for the "Interest" EJB. */ public interface InterestHome extends EJBHome { /** Creates an instance of the "InterestBean" class on the server, and returns a remote reference to an Interest interface on the client. */ Interest create() throws RemoteException, CreateException; }
-
Finally, here is the Bean class. This is the only one that does any
real work in this simple example.
Interest.java: remote interface for the `interest' EJB
-
package com.web_tomorrow.interest; import java.rmi.RemoteException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; /** This class contains the implementation for the calculateCompoundInterest method exposed by this Bean. It includes empty method bodies for the methods described by the SessionBean interface; these don't need to do anything in this simple example. */ public class InterestBean implements SessionBean { /** Calulates the compound interest on the sum "principle", with interest rate per period "rate" over "periods" time periods. This method also prints a message to standard output; this is picked up by the EJB server and logged. In this way we can demonstrate that the method is actually being executed on the server, rather than the client. */ public double calculateCompoundInterest(double principle, double rate, double periods) { System.out.println ("Someone called 'calculateCompoundInterest!'"); return principle * Math.pow(1+rate, periods) - principle; } /** Empty method body */ public InterestBean() {} /** Empty method body */ public void ejbCreate() {} /** Empty method body */ public void ejbRemove() {} /** Empty method body */ public void ejbActivate() {} /** Empty method body */ public void ejbPassivate() {} /** Empty method body */ public void setSessionContext(SessionContext sc) {} }
-
Notice that most of the methods are empty; they have to exist because
they're specified by the SessionBean interface, but they don't need to
do anything in this case.
-
Put all three of these .java files in the same directory then type
this command:
javac -d . -classpath "%JBOSS_LOC%\server\default\lib\jboss-j2ee.jar" .java
The environment variable JBOSS_LOC refers to the location of JBoss
on your system. By default, this is C:\Program Filess\jboss-4.0.2.
This should create three class files (InterestBean.class,
Interest.class, and InterestHome.class), with the package tree
starting in the current working directory (-d .). Since
InterestClient.java, which we'll talk about later, is also
in this directory, it will be compiled to InterestClient.class.
With the classes compiled, it's time to create the deployment
descriptor.
-
Step 2: the deployment descriptor
Now it's time to create the deployment descriptor. As a reminder,
this file tells the EJB server which classes form the Bean, the home
interface and the remote interface. If there is more than one Bean in
the package, it indicates also how the Beans interact with one another.
In this simple example, there is only one Bean so we won't need to
worry about that part.
Most commercial EJB servers are supplied with graphical tools for
constructing the deployment descriptor. jBoss does have an XML editor,
but it's just as easy to construct the deployment descriptor manually.
Here it is:
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <description>JBoss Compound Interest Application</description> <display-name>Compound Interest EJB</display-name> <enterprise-beans> <session> <ejb-name>Interest</ejb-name> <home>com.web_tomorrow.interest.InterestHome</home> <remote>com.web_tomorrow.interest.Interest</remote> <ejb-class>com.web_tomorrow.interest.InterestBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Bean</transaction-type> </session> </enterprise-beans> </ejb-jar>
-
The deployment descriptor must be called ejb-jar.xml and it must
be in the directory ./META-INF. A common mistake is to name this
directory "META_INF" (with an underscore, rather than a dash), which
won't work.
We also need a JBoss-specific deployment descriptor. It's called
jboss.xml and it must also be in the directory ./META-INF.
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd"> <jboss> <enterprise-beans> <session> <ejb-name>Interest</ejb-name> <jndi-name>interest/Interest</jndi-name> </session> <secure>false</secure> </enterprise-beans> </jboss>
-
So now we've got the deployment descriptors ejb-jar.xml, jboss.xml and
the classes. It's time to package them together.
-
Step 3: packaging and deploying the Bean
Creation of the Bean package involves building a JAR archive containing
the classes and the XML files. For the example Bean we have been
discussing, this is straightforward; at the top of the directory
hierarchy run jar like this:
-
set JARLIST=com/web_tomorrow/interest/Interest.class set JARLIST=%JARLIST% com/web_tomorrow/interest/InterestHome.class set JARLIST=%JARLIST% com/web_tomorrow/interest/InterestBean.class set JARLIST=%JARLIST% META-INF jar cvf interest.jar %JARLIST%
-----
jar builds an archive containing the three classes, and the XML
file in the META-INF directory. If you list the contents of the
archive you should see something like this:
-
0 06-16-00 11:34 META-INF/ 248 06-16-00 10:12 com/web_tomorrow/interest/Interest.class 300 06-16-00 10:12 com/web_tomorrow/interest/InterestHome.class 877 06-16-00 10:12 com/web_tomorrow/interest/InterestBean.class 549 06-15-00 18:15 META-INF/ejb-jar.xml 549 06-15-00 18:15 META-INF/jboss.xml
-----
Note that the directory structure must be exactly like this, or it
won't work.
To deploy the Bean on the server, all that's necessary is to copy the
.jar file to the deploy directory on the server, e.g.,
C:\Program Files\jboss-4.0.2\server\default\deploy
You can do this as often as you like; the server will detect that the
file has changed and automatically re-deploy it. During deployment
you should see the following messages from the server:
-
23:08:14,598 INFO [EjbModule] Deploying Interest 23:08:14,676 INFO [EJBDeployer] Deployed: [...]interest.jar
-----
If everything has gone according to plan, you should now have a Bean
deployed on the server. We will now create a simple test client that
runs one of its methods, just to prove that it's working.
-
Step 4: coding the test client
-
An EJB on its own is no use; we will need at least a simple client
to use its services. A user of EJBs may be another EJB, and ordinary
JavaBean, a JSP page, an applet, or a stand-alone application. In
this example, for simplicity, we will code a simple application.
This application will create an object of class Interest, and execute
its one method.
I should point out straight away that the test client I will present
below does not illustrate how you should code this sort of thing in
practice; I've done it this way to show exactly what's going on. In
reality, you will probably want to separate out the RMI-specific
stuff into a separate class to divide the important functionality
from the technical details. This class is sometimes called a `stub'.
Some EJB products will generate stubs automatically. JBoss doesn't
do this, so you'll have to code the stubs yourself (it isn't
difficult).
Here is the test client:
Test client: InterestClient.java
-
import javax.naming.*; import com.web_tomorrow.interest.*; import java.util.Hashtable; import javax.rmi.PortableRemoteObject; import com.web_tomorrow.interest.*; /** This simple application tests the `Interest' Enterprise JavaBean which is implemented in the package `com.web_tomorrow.interest'. For this to work, the Bean must be deployed on an EJB server. <p> <b>IMPORTANT</b> If you want to test this in a real client-server configuration, this class goes on the client; the URL of the naming provider specifed in the class must be changed from `localhost:1099' to the URL of the naming service on the server */ class InterestClient { /** This method does all the work. It creates an instance of the Interest EJB on the EJB server, and calls its `calculateCompoundInterest()' method, then prints the result of the calculation. */ public static void main(String\[\] args) { // Set up the naming provider; this may not always be necessary, depending // on how your Java system is configured. System.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); System.setProperty("java.naming.provider.url", "localhost:1099"); // Enclosing the whole process in a single `try' block is not an ideal way // to do exception handling, but I don't want to clutter the program up // with catch blocks try { // Get a naming context InitialContext jndiContext = new InitialContext(); System.out.println("Got context"); // Get a reference to the Interest Bean Object ref = jndiContext.lookup("interest/Interest"); System.out.println("Got reference"); // Get a reference from this to the Bean's Home interface InterestHome home = (InterestHome) PortableRemoteObject.narrow (ref, InterestHome.class); // Create an Interest object from the Home interface Interest interest = home.create(); // call the calculateCompoundInterest() method to do the calculation System.out.println ("Interest on 1000 units, at 10% per period, compounded over 2 periods is:"); System.out.println (interest.calculateCompoundInterest (1000, 0.10, 2)); } catch(Exception e) { System.out.println(e.toString()); } } }
-
It's important to understand that in reality this client will be
running on a different computer to the Bean server. For testing you
will probably run them on the same computer, but it will still be
using a network connection. So the first part of the program
indicates how to find the server.
-
System.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); System.setProperty("java.naming.provider.url", "localhost:1099");
-
There are several other ways to do this, which you may see described
in other articles. In this case, the server is on the same machine
as the client (`localhost') and the default naming port is `1099'
for jBoss (other servers may use different port numbers). If you run
the client and the server on different machines, you will need to
change these settings.
The comments in the program should describe how it works; one point
that requires mention is that the recommended way to get a reference
to the home interface on the server is like this:
-
InterestHome home = (InterestHome) PortableRemoteObject.narrow (ref, InterestHome.class);
-
which ensures compatibility with different RMI servers (e.g.,
CORBA). "narrow" ensures that the object returned in "ref" really
can be converted to an object of class "InterestHome".
The test client doesn't need to be in the same package as the EJB
classes, and in practice it probably won't be. So it needs to
import the EJB classes using their fully-qualified class name, like
this:
-
import com.web_tomorrow.interest.*;
-
You will also need to include jboss-j2ee.jar in the CLASSPATH when
compiling.
-
Step 5: running the test client
We're almost ready to run the test client, but first a note of caution.
In reality the client and the server are likely to be on different
computers. When you compile the client, the compiler needs to know
about the organization and methods of classes in the Bean so it can
do type checking. When you run the client, the run-time engine needs
to know about the Bean classes, because type-casting is done at
run-time. So when the client attempts to cast the remote reference
to a reference to an object of class InterestHome for example, it
needs to know about this class. This means that you will need the
Bean interface class files (ie. Interest.class and InterestHome.class but not InterestBean.class) on the client as well as the server even though they
execute on the server. This is fairly obvious if you think about the
logic, but it causes all sorts of problems for people who are new
to RMI programming. If the client can't find the Bean classes, you
will get error messages like this:
-
javax.naming.CommunicationException \[RootExceptionIsjava.lang.ClassNotFoundExceptionCom.web_tomorrow.interest.InterestHome|Root exception is
java.lang.ClassNotFoundException: com.web_tomorrow.interest.InterestHome\]
-
A 'communication exception' is the exception that represents any error
that can't readily be ascribed to a definite cause. If you get this
message you need to pay attention to the CLASSPATH on the client.
Even though the EJB classes will run on the server, they need to be
identified to the client so that the compiler can do the appropriate
type checking.
-
call set_jboss_loc.bat set CP=%JBOSS_LOC%\server\default\lib\jboss-j2ee.jar set CP=%CP%;%JBOSS_LOC%\server\default\lib\jboss.jar set CP=%CP%;%JBOSS_LOC%\server\default\lib\jboss-transaction.jar set CP=%CP%;%JBOSS_LOC%\server\default\lib\jnpserver.jar set CP=%CP%;%JBOSS_LOC%\lib\jboss-common.jar set CP=%CP%;. java -classpath "%CP%" InterestClient pause
-
Where the batch file set_jboss_loc.bat sets the environment variable
like this:
-
set JBOSS_LOC=C:\Program Files\jboss-4.0.2
-
Note the long CLASSPATH; it needs to include the jBoss client classes
and the EJB classes as well as the standard classpath (if any). This
needs to go in a shell script; you won't want to type it more than
once. If all is well, the test client produces the following output:
-
Got context Got reference Interest on 1000 units, at 10% per period, compounded over 2 periods is: 210.00000000000023
-
A common problem you can see here is a "class not found exception."
Make sure you include all the jars.
The Bean should produce an output on the server as well; this is to
show that the Bean has executed on the server, not the client. Look
for something like this in the server log:
-
\[Interest|Interest\] Someone called 'calculateCompoundInterest!'
-
Well, that's it. We covered coding, compiling and deploying the Bean,
and coding and running a simple test client.
Comments