Enterprise JavaBeans
As web applications become more serious, developers have become increasingly demanding about their tools. Over the last two months, we looked at two object-to-relational mapping tools (Alzabo and DODS) that make it possible to work with databases using object methods, avoiding the use of SQL inside of your program.
But there are a number of issues that many of these object-relational mapping tools fail to address: how can objects be separated onto different computers? Once separated, how can objects find each other? And if an object's state reflects the state of one or more database rows, how do we handle transactions?
These are messy and difficult questions, and we can expect to wrestle with them for years to come. One of the most comprehensive answers to these questions (and many others) is the J2EE (Java 2 Enterprise Edition) platform and its Enterprise JavaBeans object model. EJB, as it is known, is designed for use in complex, large-scale web sites and reduces the need for programmers to handle infrastructure issues.
This month, we will begin to look at EJB as implemented by the JBoss application server. JBoss is distributed under the GNU Lesser General Public License (LGPL), does not require much memory and is relatively easy to use. As is often the case with Java programming projects, working with EJB requires learning and working with a new set of tools, configuring several XML files in the right way and making sure that your CLASSPATH contains the right values during compilation and at runtime. If you can overcome these logistical hurdles, then JBoss provides an excellent basis for working with a powerful set of server-side technologies.
Before we continue, it's important to stress that neither EJB nor the Java language are truly free software. While you might not have to pay to download Java or the J2EE libraries, Sun owns everything having to do with Java, including the specifications. Sun's Community Source License is more open than many other licenses, but it is far from an open-source license.
This is particularly evident now that Lutris Corporation, who sponsored the open-source Enhydra application server, has pulled the plug on its J2EE-certified Enhydra Enterprise server. Lutris has turned Enhydra Enterprise into a closed-source project, claiming that Sun's license makes it impossible to deliver a fully compliant, open-source J2EE server. There has been a great deal of anger (and defensiveness) on the main Enhydra e-mail list, and many unanswered questions remain. Perhaps Lutris was legally (and financially) obligated to do what they did, but the manner in which they did it is an example of how not to close down an open-source project.
Luckily, the JBoss team has made it clear that JBoss will continue to be an open-source project, and that it will continue to grow and support all of the J2EE standards even if it lacks the official J2EE certification, largely because of the money required to receive such approval from Sun.
A good first question to ask is, “Why would I need EJB?” And indeed, there are many applications for which EJB is overkill. However, EJB provides functionality that would be difficult for us to implement on our own, inside of the server or container as it is known:
EJBs can reside on the same computer as your application, or on a remote computer. Thus, you can create multitiered applications in which each tier sits on a different computer, and your software continues to run unaltered as you move it from computer to computer or change the configuration of one or more tiers.
The EJB container can handle object-relational mapping issues for you. You define the database tables and the objects that map to them, and the container can handle the rest. Or if you prefer to fine-tune things yourself, you can let your bean manage its own persistence layer.
Relational databases provide transactions, allowing you to treat two or more operations as if they were a single operation. EJB gives your objects similar transactional capabilities, making it possible for a method to perform multiple actions as an all-or-nothing group.
It is also important to understand what EJB is not; despite the similar names, Enterprise JavaBeans have almost nothing to do with run-of-the-mill JavaBeans. JavaBeans have a standard API that allows us to access them from JSPs using little or no code. EJBs, by contrast, are designed to be used from any Java program, including servlets. Moreover, the standard API for EJBs is more rich, complex and flexible than that of JavaBeans. It's unfortunate that the term JavaBeans has been overloaded by these two popular server-side technologies, but there isn't anything we can do about it now.
One of the most compelling arguments for EJB is that the API is standard across application servers. Thus, you can begin working with an open-source EJB server such as JBoss, and then deploy on a commercial server when the time is ripe. (Although once you learn how much commercial servers cost, you may want to reconsider switching away from JBoss.)
Perhaps the most annoying part of working with Java is the great number of acronyms, project names and version numbers you must remember. This article works with the JDK (Java Development Kit) 1.3 and JBoss 2.4.1a server, which implements the EJB 1.1 standard. Moreover, while it is not particularly difficult to write the EJB classes themselves, the logistics associated with compiling and deploying them can be annoying and difficult for the uninitiated.
Basing your application on EJBs means moving as much of the business logic into separate objects as possible. In EJB, these objects come in two different flavors:
Entity beans are objects that map to a relational database. Each instance of an entity bean typically corresponds to a single row in a database table. Each instance variable corresponds to a single column in the database table. We normally need to define a table in our database to correspond to our entity bean, but the EJB container writes and executes SELECT, INSERT, UPDATE and DELETE queries according to our needs.
Session beans perform actions, by themselves or by using one or more entity beans. Session beans normally have no state of their own, which makes them more efficient than entity beans. However, there are times when it might help to keep some state around within a session bean. For this reason, EJB offers the stateful session bean, whose state is kept around between invocations.
If we were to create an on-line forum using EJBs, we probably would have to define entity beans (and the corresponding tables) for users, threads and postings. We also would have to define session beans that support adding, modifying and deleting each of these types of entity beans and for retrieving entire threads and individual postings.
JBoss is a Java application server that makes it possible to create and deploy multitier J2EE applications. JBoss doesn't pretend to handle the application side of things; for that, you will need to use Jakarta-Tomcat or another servlet container. But JBoss does provide the back-end features, such as a directory service and messaging service, as well as the EJB container.
Installing JBoss is extremely easy, assuming that you already have installed the JDK. Sun provides a copy of the JDK in RPM format, which you can download from java.sun.com. You also will need to download and install the Ant utility, a Java program meant to replace the venerable UNIX make program. If you are familiar with make and XML, you will find Ant's build.xml format as shown in Listing 1 [available at ftp.linuxjournal.com/pub/lj/listings/issue92/5497.tgz] to be relatively straightforward.
Once you have installed the JDK and Ant, installing JBoss is a cinch. I downloaded the binary code from <j href=“http://boss.org” target=“_blank”>boss.org, opting for the integrated JBoss and Jakarta-Tomcat support. The file comes as a zip archive, meaning that you'll need the Info-Zip utilities (which come with all Linux distributions that I've used) to unpack them.
When unpacked, the JBoss-Jakarta distribution contains two subdirectories, appropriately named jboss and tomcat. Set the JBOSS_DIST environment variable to point to the jboss directory, so that various JBoss-related utilities and features will be able to find the appropriate files.
At this point, you can start the JBoss server with the following two commands:
cd $JBOSS_DIST/bin sh run.sh
By default, JBoss logs quite a bit of information to the terminal window.
Our first EJB will be Calculator, a stateless session bean whose multiply() method takes two integers and returns their product. After writing Calculator and its necessary EJB interfaces, we will see how we can use it from within a standalone Java program.
Writing a simple Calculator class with a multiply method normally would not be very difficult. We would create the file Calculator.java and define a method with the following signature:
public int multiply (int num1, int num2)
EJB allows us to find and invoke our Calculator bean remotely, which means that we must write several classes that make it possible to find Calculator. In the end, our application will manipulate a remote reference to the actual Calculator bean, rather than the object itself. Writing a session bean thus involves writing one Java class and two interfaces.
The Java class is the bean class itself, which performs the actual work. The bean class doesn't know that it has been invoked by an object on another computer; it can learn about its environment by querying its “context” but normally doesn't need to do that very much. The bean class typically is called the EJB's simple name, with the word Bean attached. The bean class for our Calculator EJB is thus CalculatorBean, defined in the file CalculatorBean.java. The bean class must implement either the SessionBean or EntityBean interface, depending on what type of bean it is.
The first interface is the remote interface, which allows the application to locate and get a reference to the Calculator EJB. The remote interface traditionally is given a simple name, such as Calculator and thus is defined in the file Calculator.java. The remote interface should define a method for each public method in the bean class. The remote interface must extend the EJBObject class.
The second interface is the home interface, which allows the EJB container to create, locate, destroy and otherwise manage an Enterprise JavaBean. The home interface is traditionally given the same name as the remote interface, with the word Home attached. The name of our EJB's home interface is thus CalculatorHome, which we define in the file CalculatorHome.java. The home interface must extend the EJBHome class.
One of the nice things about EJB is that your classes can rely on the default EJB behavior much of the time. This might not be the most efficient way to go about things, but it allows us to focus on writing the functional part of our code, allowing the EJB container to handle almost all of the infrastructure.
Now that we understand which classes we have to create, we can begin to write some code. You'll quickly notice that there is not that much code to write, and that in the case of our CalculatorBean class, many of our methods are defined with empty bodies. This is because the SessionBean interface, from which CalculatorBean inherits, forces us to define these methods, even if our bean is simple enough not to use them. Using empty method bodies fulfills our obligations to the interface, while keeping our class simple.
I put all of the Java source files in the il.co.lerner.calculator package, reflecting the fact that they come from my commercial domain and that this is the calculator project. As such, all of the .java source files are in a directory hierarchy il/co/lerner/calculator.
Our bean class, CalculatorBean (see Listing 2), defines a single multiply() method, which takes two integer inputs and returns an integer to its caller. Other than implementing the SessionBean interface, CalculatorBean really doesn't have much to do with EJB; indeed, it is a fairly boring class with a single method. Anything we write to System.out will be printed to the JBoss session log.
Listing 2. CalculatorBean.java, the Bean Class for Our EJB Calculator
Our home interface, CalculatorHome, allows us to create a new instance of CalculatorBean. Other than defining the interface's signature, including the fact that it returns an instance of the remote interface (Calculator), the home interface is extremely short:
package il.co.lerner.calculator; import java.io.Serializable; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface CalculatorHome extends EJBHome { Calculator create() throws RemoteException, CreateException; }
Finally, our remote interface, Calculator, lists one method signature for each public method in CalculatorBean:
package il.co.lerner.calculator; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Calculator extends EJBObject { public int multiply(int num1, int num2) throws RemoteException; }Client programs will be invoking methods via the remote interface, rather than directly on the bean. The signatures for the remote interface and the bean class must match, or you will encounter serious problems later on.
Now that we have defined them, we can deploy our Calculator session bean to our running JBoss server. Deploying our session bean means taking all of its elements and turning them into a single Java archive (jar) file. Our .jar file will contain the compiled classes for Calculator, CalculatorHome and CalculatorBean.
But, it will also contain a “deployment descriptor”, an XML file named ejb-jar.xml that describes the contents of the .jar file to the EJB container. Deployment descriptors are a mandatory part of the EJB standard and do not vary from one application server to another. They tell the EJB container the names of the interfaces and classes that we have chosen, and also allow us to define such items as the type of transactions our bean will support. The deployment descriptor for our Calculator EJB is in Listing 3 and should be placed in the same directory as the .java source files.
Listing 3. ejb-jar.xml, the Deployment Descriptor for Our Calculator Bean
Our .jar file will also contain a short XML file named jboss.xml, which we will place alongside ejb-jar.xml:
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <session> <ejb-name>Calculator</ejb-name> <jndi-name>calculator/Calculator</jndi-name> </session> </enterprise-beans> </jboss>
The jboss.xml file is specific to JBoss, binding our bean to the Java's Naming and Directory Interface (JNDI). With jboss.xml in place, a client program that asks JNDI for calculator/Calculator will get a reference to it in return.
We could build the .jar file by hand, but it's easier to use Ant to build the .jar file and deploy it into the right place. Listing 1 [ftp.linuxjournal.com/pub/lj/listings/issue92/5497.tgz] contains an Ant build.xml that supports the targets ejb-jar (the default) and deploy. If you place build.xml in $CALCULATOR, then your .java files, ejb-jar.xml and jboss.xml should be in $CALCULATOR/il/co/lerner/calculator. Ant will place the results of compilation in $CALCULATOR/build/calculator, as specified in the build.calculator.dir property in build.xml.
With Ant installed in $ANT, we can compile our .java files, turn them into an EJB-compliant .jar file (with the ejb-jar.xml file in the mandatory META-INF directory) and deploy it to JBoss with the following command:
$ANT/bin/ant deploy
You should see a number of messages on the screen describing the compilation and deployment process. If the compilation or build fails, check that your environment variables are set correctly, that the Java files don't have any syntax errors and that the directories have appropriate permissions.
If your JBoss server already is running before you deploy the Calculator .jar file, you will notice that it automatically detects and deploys the file without any need for restart. This is one of the great pleasures of JBoss; in order to deploy your EJB .jar file, you simply copy it into the $JBOSS_DIST/deploy directory.
Now that we have deployed our Calculator EJB, let's write a short Java program that uses it. Listing 4 contains the source code for such a class, UseCalculator.java.
Listing 4. UseCalculator.java, Which Connects to and Uses Our Calculator EJB
While our program is completely independent from our EJB classes and can be compiled and run separately (or even on a separate computer), we use Ant to keep track of the CLASSPATH (which must include the JBoss classes, as well as those from our .jar file), compile our code and then run it. In order to run our application, we simply can say
$ANT/bin/ant use-calculator-ejb
This runs our program after ensuring that our EJB is compiled, turned into a .jar file and deployed.
Anything that UseCalculator.main() writes to System.out (also known as the stdout filehandle) is printed on the screen when we run Ant. However, anything that our CalculatorBean method writes to stdout is printed to the JBoss logging output. By keeping JBoss open in one terminal window and running Ant in another, we can see them communicate with each other.
UseCalculator's main() method consists of several standard steps for connecting to and using our EJB. We first connect to JNDI, which keeps track of the objects currently deployed to JBoss. This connection is known as a context. Our program looks for jndi.properties, a short Java properties file that tells it where it can go to find a context (this file should be placed in $CALCULATOR/resources/, as specified in build.xml). This file is in Java resources format, where every line contains name=value:
java.naming.factory.initial= java.naming.provider.url=localhost:1099 java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
Once we have our context, we look up our object using the name that we gave it in jboss.xml, which is inside of our ejb-jar.xml. Without jboss.xml, JBoss will not associate the right name with our EJB, making it impossible to find using JNDI.
JNDI returns an object reference, which we then cast into an instance of CalculatorHome, which is then used to create an instance of Calculator. Notice how we create an instance of Calculator (the remote interface), rather than one of CalculatorBean. The remote interface provides us with a transparent connection to an instance of CalculatorBean on the server, wherever that might be. At no time do we actually know where the real instance of CalculatorBean resides.
Finally, we invoke one of the methods that has been defined in Calculator (the remote interface). Our method invocation is passed along to CalculatorBean (the bean class), where it executes (and prints out some logging information) and returns (where we print the result to stdout).
This month we started to look at Enterprise JavaBeans, an infrastructure for creating distributed applications using Java. While EJB is far more complex than SOAP, XML-RPC or other distributed object systems, it is also designed to handle more complicated tasks. (For example, SOAP doesn't attempt to handle transactions; that's left to the application layer to implement.)
At the same time, working with Java often means spending more time on administrative and logistical issues, rather than on programming. Determining which file must be in which directory can often be frustrating, especially if you are used to working with a more dynamic language such as Perl or Python. Nevertheless, the pain quickly subsides when you see how easily you can create distributed applications with EJB. The fact that JBoss is so easy to download, install and run, and has a very small memory footprint, makes it simple for newcomers to try EJB.
Next month, we will continue working with EJB, looking at the heart of EJB, the entity beans that provide an object interface to our relational databases.
email: reuven@lerner.co.il
Reuven M. Lerner owns a small consulting firm specializing in web and internet technologies. He lives with his wife Shira and daughter Atara Margalit in Modi'in, Israel. You can reach him at reuven@lerner.co.il or on the ATF home page, www.lerner.co.il/atf.