The K-Zone: First steps with Enterprise JavaBeans using jBoss 3.2

This article presensts a step-by-step tutorial on how to set up jBoss, the open-source Enterprise JavaBeans (EJB) server, and then create, deploy and test your first EJB component. It doesn't explain what EJBs are, or how they are used; there are a number of good introductory articles on Sun's JavaSoft Web site. And, of course, I can't resist the temptation to recommend my own book on the subject: Applied Enterprise JavaBeans technology, published by Prentice Hall. In addition, this article skips over a lot of complexity, by making the EJB itself as simple as it could possibly be. To speed things up, we will allow jBoss to use default settings for lots of things that you would probably want to change in a real project.
      This article is based on a development technique that uses simple, command-line tools and a text editor, nothing more. There are, arguably, easier and faster ways to implement EJB projects. However, I hope that by doing it this way it will be absolutely clear exactly what is going on. Nothing is hidden. In a real project you probably wouldn't want to create your own deployment descriptors using a text editor, but it is a good way to understand the details in a simple example such as this. In practice, you wouldn't want to type all these commands every time you use them, even if you favour this method of development -- you'd probably put them in a shell script or an Ant build script.

A note for Microsoft Windows users

This article focuses on the use of command-line tools. Using such tools probably comes more naturally to a Unix developer than to a Windows developer. The command-lines used below are appropriate for a Unix system. However, they will all work on the Windows DOS prompt as well. You'll just need to remember the following translations.

About the EJB

In this article we will create the simplest of simple EJBs: a stateless session bean with one method. The purpose of this EJB is to calculate compound interest on sums of money, and is therefore called Interest. The one method provided has the following signature:
double calcInterest(double principal, double rate, int periods);
We will package this EJB into its own JAR file, and deploy that JAR file on jBoss. We'll then write a test client to run the EJB's single method within the jBoss EJB container.

Preliminary checks

Ensure that you have a modern (1.4.1 or later) java JDK, and you can run its tools at the command line. For example:
java -version
Should give a version number, not a `file not found' message. Check that the javac and jar commands can also be invoked correctly.

Step 1: download and install jBoss

You can get jBoss from the jBoss Web site. At the time of writing, the latest version is 3.2.5. It is available in various package formats (tar, zip, bz2, etc); they are identical except for the packaging method. I'll assume that you have downloaded jboss-3.2.5.tar.gz.

Installation consists of unpacking the download into a suitable directory, which I shall call JBOSS_HOME in this article

$ cd $JBOSS_HOME
$ tar xvfz jboss-3.2.5.tar.gz

Step 2: start jBoss

Just do:
$ cd $JBOSS_HOME/bin
$ ./run.sh 
or Unix, or
> cd %JBOSS_HOME%\bin
> run.bat 
on Windows.

A successful startup produces about two hundred lines of output, and finishes up something like this:

10:35:26,090 INFO  [JkMain] Jk running ID=0 time=1/290  config=null
If it doesn't get this far, something is wrong. Most likely it will be that an IP port number jBoss uses is already in use by something else, or the Java version is not recent enough. Information on configuring these settings can be found in the documentation on the jBoss Web site.

A particular important directory is the client directory under JBOSS_HOME. In this directory are all the JAR files containing the EJB API classes, as well as the classes that support EJB test clients. I will call this directory LIBDIR in the rest of this article. You might want to set an environment variable to point to this directory to make the commands quicker to type (some will be very long).

Step 3: create the source tree

If you're an experienced developer you'll no doubt have your own ideas on how to structure a source tree. The structure I use here has worked for me for several years, and is particularly appropriate where the sharp distinction between server-side code and client-side code has to be maintained. If you don't want to create the structure and type in all the code yourself, you can download the source package here. When unpacked, this creates the structure discussed below.
      At the top level of the source tree we will have the following directories: src for source code, target for compiled classes, etc for configuration files, and assemble for the generated JAR files.
      The source (src) directory has three subdirectories: server, client, and shared. server is for classes that will only be deployed onto the server; client is only for the test client; shared is for classes that are required by both the server code and the test client. In this case, the shared code will be the home interface and component interface (what we used to call the `remote interface') of the EJB. The server needs these interfaces to build the supporting infrastructure for the EJB; the client needs them because they expose the EJB's callable methods.
      The target directory has two subdirectories: server and client. The shared code will appear in both these directories when it is compiled.
      The EJB's classes will be in the Java package com.kevinboone.ejb, so you'll need to create a matching directory structure under src/server, src/client, and src/shared. In short, the source tree has the following structure:
src
  server
    com
      kevinboone
        ejb
  shared
    com
      kevinboone
        ejb
  client
    com
      kevinboone
        ejb
etc
target
  server
    META-INF #see below for why we need this!
  client
assemble

Step 4: create the EJB interfaces and implementation class

The home and component interfaces will go into the directory src/shared/com/kevinboone/ejb, as they are shared between the server and the test client. Create them with your favourite text editor. Here is the home interface -- note that it exposes only one create() method. Clients will use this method to tell the EJB container to create new instances of the EJB. This file should be called InterestHome.java.
package com.kevinboone.ejb;
import javax.ejb.*;
import java.rmi.*;
                                                                                
/*
(Remote) home interface for the Interest EJB
*/
public interface InterestHome extends EJBHome
{
// All home interface methods must be declared to
//  throw CreateException; conventionally the
//  implementation will throw this if the EJB
//  instance cannot be created.
public Interest create()
  throws CreateException, RemoteException;
}
And here is the remote interface -- it also exposes only one method, the calcInterest() method that clients will call. This file should be called Interest.java
package com.kevinboone.ejb;
import javax.ejb.*;
import java.rmi.*;
                                                                                
/*
(Remote) component interface for the Interest EJB
*/
public interface Interest extends EJBObject
{
// Note that all business methods must be declared
//  to throw a RemoteException. You'll get this
//  exception in your client if there is a failure
//  in the EJB infrastructure
                                                                                
public double calcInterest
  (double principal, double rate, int periods)
    throws RemoteException;
}
The bean implementation class goes in directory src/server/com/kevinboone/ejb, as it appears only on the server, never the client. Call the file InterestBean.java.
package com.kevinboone.ejb;
import javax.ejb.*;
                                                                                
/*
Implementation class for the Interest EJB
*/
public class InterestBean implements SessionBean
{
// The following methods are just dummies in this
//  simple example; they don't have to do anything
public void ejbCreate() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbRemove() {}
public void setSessionContext(SessionContext sc) {}
                                                                                
// calcInterest()
public double calcInterest (double principal, double rate, int periods)
  {
  // Write some output to the server log, so we know
  //  the method has been called
  System.out.println ("Someone called calcInterest()");
  double result = principal;
  // Do the math -- accountants please don't write in :)
  for (int i = 0; i < periods ; i++) result = result * (1 + rate);
  return result;
  }
}

Step 5: compile the server-side code

At this point, our source tree should look like this:
src
  server
    com
      kevinboone
        ejb
          InterestBean.java
  shared
    com
      kevinboone
        ejb
          Interest.java
          InterestHome.java
  client
    com
      kevinboone
        ejb
etc
target
  server
    META-INF #see below for why we need this!
  client
assemble
We now need to compile the server-side code into the directory target/server. Here's how:
javac -classpath $LIBDIR/jboss-j2ee.jar \
-d target/server \
src/shared/com/kevinboone/ejb/*.java \
src/server/com/kevinboone/ejb/*.java
Remember to switch the forward slashes for Windows, and put the whole thing on one very long line. The file jboss-j2ee.jar. that we're including on the classpath contains the API definitions, that is, the code for interfaces such as javax.ejb.SessionBean. The compiler won't get very far if you try to compile an EJB without this.
      With this command we're compiling the shared and server classes together, and using the -d switch to put the output in the right place. We should now have a source tree that looks like this:
src
  server
    com
      kevinboone
        ejb
          InterestBean.java
  shared
    com
      kevinboone
        ejb
          Interest.java
          InterestHome.java
  client
    com
      kevinboone
        ejb
etc
target
  server
    META-INF #see below for why we need this!
    com
      kevinboone
        ejb
          Interest.class
          InterestHome.class
          InterestBean.class
  client
assemble
Note that the target/server directory now contains the interfaces and the implementation class together.

Step 6: create the deployment descriptor

The deployment descriptor must be called ejb-jar.xml (that what it says in the EJB Spec, not a jBoss restriction). I suggest creating it in the etc directory of the source tree. The deployment descriptor should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC 
"-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" 
"http://java.sun.com/dtd/ejb-jar_2_0.dtd">
                                                                                
<ejb-jar>
 <enterprise-beans>
  <session>
   <ejb-name>Interest</ejb-name>
   <home>com.kevinboone.ejb.InterestHome</home>
   <remote>com.kevinboone.ejb.Interest</remote>
   <ejb-class>com.kevinboone.ejb.InterestBean</ejb-class>
   <session-type>Stateless</session-type>
   <transaction-type>Container</transaction-type>
  </session>
 </enterprise-beans>
</ejb-jar>
The deployment descriptor simply indicates to the server the names of the classes and interfaces that make up the EJB, the name of the EJB, and some basic management properties. In a real EJB you could expect to find transaction properties, security properties, container-managed relationships, persistence settings, and all sorts of other stuff, but we don't need any of that today.

Step 7: build the deployable JAR file

We're now in a position to construct the JAR file for deployment on the server. First, copy the deployment descriptor ejb-jar.xml into target/server/META-INF. Then:
$ cd target/server
$ jar cfv ../../assemble/interest.jar .
$ cd ../..
The full-stop (.) on the end of the jar command means `include everything in the current directory'. This command will leave the JAR file in assemble. If you inspect this JAR, for example by running
jar tf assemble/interest.jar
You should see that the JAR has the following structure:
META-INF/
META-INF/MANIFEST.MF
./
com/
com/kevinboone/
com/kevinboone/ejb/
com/kevinboone/ejb/InterestHome.class
com/kevinboone/ejb/Interest.class
com/kevinboone/ejb/InterestBean.class
META-INF/
META-INF/ejb-jar.xml
It must have this structure; that is, the directory paths must match the Java package names, and ejb-jar.xml must be in META-INF. This, again, is a requirement of the Specification and is intended to make life easier for the EJB container. It has enough to do as it is, without hunting around for class files.

Step 8: deploy

You're now ready to deploy the JAR on jBoss. This is very easy; simply copy the new JAR file into jBoss's deployment directory. The deployment directory is configurable; if you haven't changed it, it is JBOSS_HOME/server/default/deploy. So deployment is no more than this:
$ cp assemble/interest.jar $JBOSS_HOME/server/default/deploy/
After a second or two, jBoss should spring into life, and you should see messages in its console to the effect that it has detected and deployed a new JAR.
11:36:31,533 INFO  [EjbModule] Deploying Interest
11:36:31,824 INFO  [EJBDeployer] Deployed: file: interest.jar
When you deploy, jBoss automatically checks the structural integrity and Specification compliance of the JAR file. If this check fails, the JAR has not been deployed. The error messages are relatively helpful; for example:
Bean   : Interest
Method : public abstract double 
  calcInterest(double, double, int) throws RemoteException
Section: 7.10.5
Warning: The methods defined in the remote interface must have a matching
method in the bean's class with the same name and same number and types of
arguments.
Not only has jBoss detected a violation of the EJB Specification, it has indicated which section of the Specification to look at (7.10.5 in this case). If you see messages like this, you need to correct the code.

Step 9: writing the test client

The test client will be a class InterestClient, also in package com.kevinboone.ejb. The source should go into the directory src/client, as this code is client-side only. So, we need a file InterestClient.java, in src/client/com/kevinboone/ejb.
package com.kevinboone.ejb;
import javax.ejb.*;
import java.rmi.*;
import javax.rmi.*;
import javax.naming.*;
                                                                                
public class InterestClient
{
public static void main (String[] args) throws Exception
  {
  Context c = new InitialContext();
  Object o = c.lookup ("Interest");
  InterestHome ih = (InterestHome)
    PortableRemoteObject.narrow (o, InterestHome.class);
  Interest i = ih.create();
  double amount = i.calcInterest(1000, 0.1, 2);
  System.out.println ("Result is: " + amount);
  i.remove();
  }
}
In this client, we do a lookup for the EJB called Interest. For future reference it is important to realize that the name Interest here does not have to match the name you supplied in the deployment descriptor:
   <ejb-name>Interest</ejb-name>
jBoss defaults to allowing clients of EJBs to use the ejb-name tag as a lookup name; most EJB products don't. Typically you'll need to create a separate XML file that denotes the names that clients will use for EJBs (`JNDI names' in EJB jargon). jBoss does let you do it this way as well, but in this simple example there is no potential for name clashes, so we don't need to. Note also that this horrible construct:
 InterestHome ih = (InterestHome)
    PortableRemoteObject.narrow (o, InterestHome.class);
is required for compatibility when the server-side EJB infrastructure is not from the same vendor as the client-side EJB infrastructure. In practice we could have got away with the much less ugly:
 InterestHome ih = (InterestHome)o;
but structly speaking this violates the Specification.

Step 10: compile the test client

Now we'll compile the test client. Because it uses the EJB's home and component interfaces, which are shared code, we'll need to compile in the src/shared directory as well. So the full command is:
javac -classpath $LIBDIR/jboss-j2ee.jar \
-d target/client \
src/shared/com/kevinboone/ejb/*.java \
src/client/com/kevinboone/ejb/*.java
This will put the test client compiled class, and the compiled classes for the home and components interfaces, into target/client.

Step 11: create the JNDI properties file

The test client we are creating is entirely separate from the server. It will run in a separate JVM, perhaps on a different physical host (this is, of course, possible -- EJB is a distributed architecture after all). The client uses JNDI to find the naming server that can give it a reference to the EJB's home object, but the JNDI implementation does not know where the naming server is, or what protocol it uses. In practice, the naming server is likely to be the same physical host as an EJB server, but the JNDI system does not know where the EJB server is either. This complication does not arise when you are building a self-contained application (perhaps with servlets and JSPs as well as EJBs), because if you deploy the whole application in one go, the server always knows where everything is (because you've just deployed the entire system). With a stand-alone client, you have to give the JNDI implementation a bit of help.
      There are various ways to tell the JNDI system how to talk to the naming server. You can do it in code, but passing arguments to the constructor of InitialContext. Or you could specify the relevant properties as system properties to the client JVM. In this example, I will use a jndi.properties file. Java's standard JNDI implementation will always search the classpath to find a file with this name. It should contain at least two properties: the name of a class that it can use to communicate with the naming server, and the URL of the naming server. Both these properties are vendor-specific, and you'll need to consult the server documentation to find out what is required for a specific product. For jBoss, we need something like this:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
I'd suggest putting this jndi.properties file into etc along with the deployment descriptor. The `localhost' in this file indicates that the test client will look for a name server on the same host as itself; if you want to try running the EJB across a network, you'll need to substitute the jBoss server's hostname in here instead. Note that jnp, it the `jBoss naming protocol'. It is specific to jBoss. Other products use other protocols, but for cross-platform compatibility all must be able to use the CORBA IIOP (Internet Intra-Orb Protocol). JNP is better for this example, because it supports dynamic stub downloading (more of which later).

Step 12: package the test client

You don't need to package the test client classes into a separate JAR, but it's convenient to do so, especially if you plan to run the test client on a separate physical host to the server (and this is, of course, possible -- EJB is a distributed architecture after all). So, let's build a interest-client.jar in assemble like this:
cp etc/jndi.properties target/client
cd target/client
jar cfv ../../assemble/interest_client.jar .
cd ../..
So, finally, our source tree looks like this:
src
  server
    com
      kevinboone
        ejb
          InterestBean.java
  shared
    com
      kevinboone
        ejb
          Interest.java
          InterestHome.java
  client
    com
      kevinboone
        ejb
etc
  ejb-jar.xml
  jndi.properties
target
  server
    META-INF #see below for why we need this!
    com
      kevinboone
        ejb
          Interest.class
          InterestHome.class
          InterestBean.class
  client
    com
      kevinboone
        ejb
          Interest.class
          InterestHome.class
          InterestClient.class
assemble
  interest.jar        #server package
  interest-client.jar #client package
We've ended up with two JAR files: one containing classes relevant to the server, plus shared classes; the other containing the test client, plus shared classes.

Step 13: run the client

So we come to the moment of truth: can we run the deployed EJB with our test client? The complication here is that the client needs a whole heap of supporting classes on its classpath. We didn't worry about this when deploying the EJB JAR file, because its classpath is the classpath of the jBoss server, which we assume is already correctly set up. But the client is a different matter: it needs to have access to the jBoss JNDI classes, stub downloading mechanism, network protocols, etc., as well as to the EJB API classes. Consequently we need a very long classpath to run the client. The command looks like this:
java -classpath assemble/interest_client.jar:\
$LIBDIR/jnp-client.jar:\
$LIBDIR/jboss-common-client.jar:\
$LIBDIR/jboss-j2ee.jar:\
$LIBDIR/jboss-net-client.jar:\
$LIBDIR/jbossall-client.jar \
com.kevinboone.ejb.InterestClient
It should be obvious why I suggested defining an environment variable LIBDIR earlier in this article. If you don't you have an awful lot of typing.
      If it works, you should see some output from the client showing that it has calculated the amount of interest. More importantly, if you look in the console running the jBoss server, you should see something like this:
12:05:25,513 INFO  [STDOUT] Someone called calcInterest()
Indicating that the calcInterest() method has been run on the EJB server.

Step 14: sit back and think about what you've done

In this example we compiled and deployed a simple distributed application. It was distributed because the Java class called was running in one process, and the Java class doing the calling was in another. You may even have had the caller and the target class on different physical hosts, and made the method call between them. But you did not need to write any networking code, or even take account of networking when coding the EJB. All the network stuff is abstracted away.
      When the client runs this code:
  Interest i = ih.create();
  double amount = i.calcInterest(1000, 0.1, 2);
The entity i must be a Java object, because you can't call methods on anything except objects. Yet Interest is an interface, not a class. So what is it that implements the interface Interest? It isn't InterestBean (look at the code again, if you don't believe this). So we appear to have an interface that isn't implemented by anything, but can still methods called on it. The reality is that Interest is implemented by a stub, that is, a piece of communications code generated by the EJB server. jBoss built the stub for your Interest EJB when you deployed the EJB itself. The stub contains all the same methods as the EJB itself (or, at least, all those that are exposed to clients), but no business logic. When the client calls calcInterest() it is really calling this method on the stub; the stub then talks to the EJB server, which calls the calcInterest() method on InterestBean. The stub and the server organize the passing of arguments and return values over the wire.
      But how did the stub get onto the client in the first place? Remember that the client and the server could be on different hosts. When you deploy the EJB, the stub gets built on the server, but how does it get to the client? Well, either you can copy the stub onto the client yourself, or you can rely on the magic of dynamic stub downloading. When the client uses the jBoss built-in naming protocol, the stub is automatically transferred to the client as part of the name-lookup operation. This means, in effect that the whole of the EJB network infrastructure is completely invisible to the developer.
©1994-2006 Kevin Boone, all rights reserved