Now that we’ve covered the concepts behind the various component models and container services provided by the specification, it’s time to start putting these lessons into practice. In this topic we’ll introduce the terminology and syntax necessary to code, package, deploy, and test some functional EJBs.
Step 1: Preparation
Let’s briefly lay out some definitions and conventions, and then we’ll get to hacking away.
Definitions
EJBs are composed from the following.
Bean implementation class (session and message-driven beans)
This class, written by the bean provider, contains the business logic for a session or is all that’s needed to declare an SLSB. Bean instance (session and message-driven beans)
The EJB Container internally contains one or more instances of the bean implementation class to service session or message-driven invocations. These are not exposed to the client, but are instead abstracted out by way of the client view. Entity bean instances are POJOs that may be created directly by the client using the new operator.
Client view (session and message-driven beans)
Because clients do not invoke upon bean instances directly, this is the contract with which a client will interact with the EJB. In the case of a session bean, the client view will take the form of a business, component, or endpoint interface—all to be implemented by the EJB Proxy. Message-driven beans have no client view, as they’re received as messaging events, and entity beans also have no explicit view because clients will interact with a managed object directly. At least one view must be defined (either explicitly or implicitly) for an EJB.
EJB Proxy (session beans)
Session bean clients invoke upon the EJB Proxy, which adheres to the contract defined by the client view. Upon EJB deployment, the Container binds all appropriate Proxy objects into a predefined destination in Global JNDI (EJB 3.1 Specification Section 4.4), where a client may look up the reference. From the client’s perspective, the proxy is the EJB, though in actuality the invocation mechanism is a bit more involved (Figure 4-1).
Figure 4-1. The role of the client, container, and bean instance in an EJB invocation
It’s because of the Proxy indirection that the container is able to perform its extensive services outside of the client’s concern.
Local versus remote (session beans)
The terms local and remote, when referring to business, home, or component interfaces, designate the relationship between the process running the client and the JVM running the EJB Container. This is often confused with distribution across physical boundaries. When the client is in-VM, invocations to the local interface are much more efficient than their remote counterpart; they may avoid the networking stack and pass object references instead of serializing values. For instance, EJBs within an application will often communicate with each other by means of a local business interface. The EJB 3.1 Specification details key differences between local, remote, and endpoint views in Section 3.2.
Business interface (session beans)
This is the simplified view, introduced in EJB 3.0, defining business methods that are to be supported by session beans. The business interface is marked as such via an @javax.ejb.Local or @javax.ejb.Remote annotation, depending upon how it’s to be used. However, any one interface may not be both local and remote. The EJB 3.1 Specification defines business interfaces in Section 4.9.7.
Component interface (session beans)
This is the legacy view defined by the EJB 2.x Specification. It has been left in place for backward compatibility with older clients, and it is detailed in EJB 3.1 Section 3.6. The Component interface (also known as the “local” or “remote” interface, contrasted with business interfaces, which are called “business local” or “business remote”) must implement javax.ejb.EJBObject or javax.ejb.EJBLocalObject and will additionally define business methods available to the client. It is obtained via the create<METHOD> methods of the home interface.
Home interface (session beans)
The home interface is used to create, find, and remove session bean references according to the EJB 2.x client contract. Home interfaces extend javax.ejb.EJBHome or javax.ejb.EJBLocalHome and may define methods named with a prefix of “create” to return component interface references. Just like component interfaces, these are legacy and in place to support backward compatibility.
Endpoint interface (session beans)
The endpoint interface defines business methods that can be accessed from applications outside the EJB container via SOAP. The endpoint interface is based on Java API for XML-RPC (JAX-RPC) and is designed to adhere to the SOAP and WSDL standards. The endpoint interface is a plain Java interface that is annotated with the @javax.jws.WebService annotation.
Message interface (MDBs)
Message-driven beans implement the message interface, which defines the methods by which messaging systems, such as the JMS, can deliver messages to the bean. An MDB that listens to JMS events would therefore be defined like this:
Naming Conventions
Before going on, let’s establish some conventions to be used throughout this topic. These will be used for clarity; they are not prescriptive or even recommended for use in production. You may use any naming strategy you wish, but it’s suggested that you stick to some predefined standard. There is no law dictating that each of the constructs listed next must be used for your EJBs; in fact, in most cases you’ll use just one or two. The impositions of the EJB 2.x specifications are long gone.
Common business name
To describe an EJB’s function, we’ll assign it a common base from which we may derive other names. This should be descriptive of the business function; therefore, an EJB that handles ticket processing may contain the term Ticket Processor. All other classes that comprise an EJB may then be named according to the following table:
Construct |
Suffix |
Example |
EJB (the sum of its parts) |
EJB |
TicketProcessorEJB |
Bean Implementation Class |
Bean |
TicketProcessorBean |
Remote Business Interface |
RemoteBusiness |
TicketProcessorRemoteBusiness |
Local Business Interface |
LocalBusiness |
TicketProcessorLocalBusiness |
Remote Home Interface |
RemoteHome |
TicketProcessorRemoteHome |
Local Home Interface |
LocalHome |
TicketProcessorLocalHome |
Remote Component Interface |
Remote |
TicketProcessorRemote |
Local Component Interface |
Local |
TicketProcessorLocal |
Endpoint Interface |
WS (for WebService) |
TicketProcessorWS |
Conventions for the Examples
To maintain consistency, this text will adhere to the following conventions unless explicitly specified.
Vendor-agnostic
All examples covered in Parts I through IV of this topic will address syntax and concepts dictated by the EJB Specification only. As the EJB deployment mechanism is not covered by spec, these examples will not run unless bolstered by some additional instructions or metadata. To this end, each example will have a deployable counterpart in Part V, which is written to work specifically with the open source JBoss Application Server. The focus of the code fragments in this text may be intentionally incomplete to promote brevity, and they are in place primarily to illustrate concepts. Part V will serve as the real practice grounds for getting your hands dirty. You may want to flip back and forth between the lessons and their corresponding examples as you progress.
No XML
Generally speaking, EJB metadata may be supplied either by a standard XML descriptor (ejb-jar.xml), Java annotations, or some combination of the two. In cases where there’s overlap, XML wins as an override. In order to cut down on the verbosity of our examples, we’ll opt for annotations wherever possible. The corresponding XML syntax is available for reference by consulting the appropriate Document Type Definition (DTD) or schema.
Annotations on the bean implementation class
Many class-level annotations may be applied in a variety of locations. For instance, a remote business interface may be marked on the interface itself:
Instead, we’ll be declaring annotations upon the bean implementation class:
This means that a quick glance will reveal all metadata for the bean in question, and we’ll minimize poking through various files to get the full picture.
Step 2: Coding the EJB
For this exercise we’ll make a simple SLSB, a Calculator Service, to illustrate how the various pieces all fit together. In fact, we’ll spin up two EJBs—one to show the path of least resistance, and another to expose all business, home, and component views.
The Contract
This is a great starting point, regardless of whether you’re building EJBs. The contract, implemented as interfaces in Java, defines what our service will do, and leaves it up to the implementation classes to decide how it’s done. Remember that the same interface cannot be used for both @Local and @Remote, so we’ll make some common base that may be extended.
public interface CalculatorRemoteBusiness extends CalculatorCommonBusiness{}
As you can see, we’ve created CalculatorRemoteBusiness by extending the contract of CalculatorCommonBusiness. This will come in handy later when we want to add more views exposing the same method (so we don’t have to rewrite its definition). Our remote business interface meets the requirement that our session bean will have at least one view, so we may now write the bean implementation class.
The Bean Implementation Class
Again we’ll make a common base to contain the logic, and extend it to add our metadata that will define the SLSB.
This contains the required implementation of CalculatorCommonBusiness. add(int…). The bean implementation class therefore has very little work to do.
The function of our bean implementation class here is to bring everything together and define the EJB metadata. Compilation will embed two important bits into the resultant .class file. First, we have an SLSB, as noted by the @Stateless annotation. And second, we’re exposing a no-interface view, new to EJB 3.1, courtesy of the @Local
Bean annotation. Alternatively, we could have used an XML descriptor to hold this information, making an EJB out of CalculatorBeanBase. The EJB Container, upon deployment, will pick up on this information and take the necessary actions.
Before we use our new classes as an EJB, let’s leverage the pure POJO programming model to make some quick tests.
Out-of-Container Testing
Unit testing, as contrasted with integration testing, aims to isolate small bits of functionality and assert that they’re working as expected. The addition implementation of our CalculatorEJB is an excellent candidate for this kind of approach. By treating our EJB Implementation class as a POJO, we can keep test setup and execution time light and quick.
Testing in this topic will be implemented using the popular open source framework JUnit (http://www.junit.org/):
Here we make a new instance of SimpleCalculatorBean and use it to add a few integers. Again, this is not an EJB. The example showcases how the EJB Programming Model does not tie you to EJB at all; the test has no knowledge of the specification APIs.
Integration testing is where we’ll ensure that the individual pieces play nicely together.
Integration Testing
There are three steps involved in performing integration testing upon an EJB. First, we must package the sources and any descriptors into a standard Java Archive (JAR; http: //java.sun.com/javase/6/docs/technotes/guides/jar/jar.html) or Enterprise Archive (EAR; http://java.sun.com/javaee/5/docs/tutorial/doc/bnaby.html#indexterm-47). Next, the resultant deployable must be placed into the container according to a vendor-specific mechanism. Finally, we need a standalone client to obtain the proxy references from the Container and invoke upon them.
Packaging
The Sun Microsystems JDK, for example, ships with a standard jar tool that can be used to assemble classes, resources, and other metadata into a unified JAR file, which will both compress and encapsulate its contents. Typically your project’s build will handle this step, often by way of an Apache Ant (http://ant.apache.org/) task or Maven (http://maven.apache.org/) goal. Whichever route you choose in packaging, the result might look a little bit like this if we were to print out the contents:
The ejb-jar.xml line represents our optional XML descriptor, which is excluded from this example.
Deployment into the Container
The EJB Specification intentionally leaves the issue of deployment up to the vendor’s discretion. Therefore, our examples will keep this out of scope until Part V, which will fully detail instructions for running examples.
The client
Again we’ll implement our client as a JUnit test. This time, instead of creating POJOs via the new operator, we’ll look up true EJB references via JNDI. For our purposes, JNDI is a simple store from which we may request objects keyed to some known address. The form of the address is supplied by the EJB 3.1 Portable Global JNDI Name Syntax.
Let’s walk through this step-by-step. The process looks a bit like the following:
1. Perform initialization of the test, and set up the JNDI Environment.
2. Look up the proxy reference to the EJB via JNDI.
3. Invoke upon the reference.
4. Test the result.
First, we declare some imports to show where some nonobvious types are coming from. Assume that the client is in the same package namespace as the CalculatorEJB classes:
The javax.naming classes are used in obtaining the proxy references from JNDI, and the others are part of the test framework. Now to define the class and make for some static members used in testing:
Here we’ve declared a JNDI naming Context for the lookups, and the names of the target JNDI Addresses under which we’ll find the appropriate proxy reference. Our tests will check that the EJB 3.1 Business View works as expected, but before the test runs, a lifecycle method will obtain the reference and set the proxy reference accordingly:
First we create the naming Context, using a standard jndi.properties file that we assume to be present on the classpath. The JNDI properties to be used are vendor-specific, so by pulling these out into a separate file, our test remains portable across EJB implementations.
From there, we obtain the business proxy by performing a simple JNDI lookup upon the portable Global JNDI name.
Let’s define a central point to perform assertions within our tests:
This time, the calc reference passed in will not be a POJO, but instead a true EJB Proxy. .
Our two tests will merely pass the appropriate EJB Proxy to the assertion function. Assuming everything goes well, we’ll get the all clear and may rest assured that the system is working as we expect.
The interesting thing to note about this setup is that nowhere have we declared the notion of remoting, transactions, or the like. Regardless, this test is a fully functional remote client to a distributed architecture. Client code has no idea that it’s crossing process and network boundaries. This is a powerful argument in favor of the EJB Architecture.