By Ken Saks
The Enterprise JavaBeans (EJB) 3.0 specification introduced a new, vastly simpler, API for implementing and accessing session beans. Many developers have questions about how applications written to this new API can interact with legacy EJB applications, or how legacy applications can interact with the new API.
It would be impractical to require developers to entirely rewrite all existing EJB applications in order to take advantage of any of the new capabilities. In some cases, developers plan to upgrade their applications, but would prefer to do it using a phased approach. In other cases they prefer to write new EJB 3.0 applications, but would still like to take advantage of their existing application's services. The EJB 3.0 specification makes both of these strategies possible. This tip explains how.
A sample application package accompanies the tip. The code examples in the tip are taken from the source code of the sample (which is included in the package). The sample uses the Java EE 5 SDK. You can download the Java EE 5 SDK from the Java EE Downloads page
EJB 3.0-to-EJB 2.x Compatibility
Let's first look at an example of an application written to the simplified EJB 3.0 API calling a legacy EJB application. In the source code for the sample that accompanies this tip you'll find an EJB 3.0 stateless session bean namedMigrationBean
. The bean invokes a remote EJB 2.x stateless session bean named EJB2xBean
. MigrationBean
could either be an entirely new bean or an existing bean that was recoded to use the EJB 3.0 API. For simplicity both beans in this example are packaged in the same archive, but that is not required. Here are the EJB 2.x home and remote interfaces for EJB2xBean
:
public interface EJB2xHome extends EJBHome {
public EJB2xRemote create()
throws CreateException, RemoteException;
}
public interface EJB2xRemote extends EJBObject {
public void foo() throws RemoteException;
}
Here is the first part of the MigrationBean
class:
@Stateless
public class MigrationBean implements MigrationRemote {
// Inject a reference to the Home interface
// of the EJB 2.x bean.
@EJB private EJB2xHome ejb2xHome;
private EJB2xRemote ejb2x;
MigrationBean
uses the @EJB
annotation to inject a reference to EJB2xHome
. The @EJB
annotation was introduced as part of the EJB 3.0 specification. However, it was designed to be used with the older-style EJB 2.x home view in addition to the EJB 3.0 business interface view. Whenever the EJB container instantiates MigrationBean
, it injects a reference toEJB2xHome
in the ejb2xHome
data member.
An important benefit of using the @EJB
annotation with a 2.x home interface is that it frees you from having to usePortableRemoteObject.narrow()
. The PortableRemoteObject.narrow()
method is used to cast to the correct type the Object
returned when you lookup the home interface. In EJB 3.0 the injected home reference can be used without any special casting.
The MigrationBean
class defines a @PostConstruct
method that uses the home reference to create an EJB2xRemote
reference. The class then stores the result in another data member.
@PostConstruct
private void initialization() {
try {
ejb2x = ejb2xHome.create();
} catch(Exception e) {
throw new EJBException(e);
}
}
In the last part of the class, MigrationBean
invokes the EJB2x business method within its own business method.
public void bar() {
try {
// Call operation on legacy EJB.
ejb2x.foo();
} catch(Exception e) {
throw new EJBException(e);
}
}
All standard EJB services such as security and transaction propagation work as expected between the two beans.
Note that using an @EJB
annotation to access EJB 2.x beans is not limited to EJB components. You can use the same approach to access a legacy bean from other types of components in a Java EE 5 application, such as a servlet or an application client. Also, even though this example used a remote EJB 2.x bean, you can use the approach to access a local EJB 2.x bean as well.
EJB 2.x-to-EJB 3.0 Compatibility
In the first example, you saw how an EJB component written using the new EJB 3.0 API could be a client of a legacy EJB component. Now let's look at the reverse case. Here you'll see how a legacy component that is coded using the EJB 2.x client API can access a session bean that is implemented using the EJB 3.0 simplified API. The idea here is to use the new EJB without making any changes to the existing EJB client code.
In this second example, a stateful session bean named AdaptedBean
is implemented using the simplified EJB 3.0 API. The bean exposes a 2.x remote home interface to a standalone Java client. Here are the home and remote interfaces for AdaptedBean
:
public interface AdaptedHome extends EJBHome {
public AdaptedRemote create(String id)
throws CreateException, RemoteException;
}
public interface AdaptedRemote extends EJBObject {
public String getId() throws RemoteException;
}
Here is the AdaptedBean
class :
@Stateful
@RemoteHome(AdaptedHome.class)
public class AdaptedBean {
private String myId = "unknown";
@Init
public void create(String id) {
myId = id;
}
public String getId() {
return myId;
}
}
Notice that the bean class is implemented using the POJO-like simplifications of EJB 3.0. However, it uses the@RemoteHome
annotation to exposes an EJB 2.x style remote home interface. This annotation tells the EJB container that the bean has a home interface named AdaptedHome
and a remote interface named AdaptedRemote
. You don't need a separate annotation to specify the AdaptedRemote
interface. That's because the container can derive it from the signature of the AdaptedHome
's create()
method. If the bean exposed a local home interface, it would use the @LocalHome
annotation instead.
The other important annotation in the class is @Init
. The EJB 2.x view always defines a create
method, so you must define a method on the bean class to handle the create operation. The @Init
annotation tells the EJB container which method that is. The method can have any name, but its method parameters must match the create
method in the corresponding home interface.
Here is the standalone client code for accessing AdaptedBean
:
try {
InitialContext ic = new InitialContext();
Object o = ic.lookup(AdaptedHome.class.getName());
AdaptedHome adaptedHome = (AdaptedHome)
PortableRemoteObject.narrow(o, AdaptedHome.class);
AdaptedRemote adapted = adaptedHome.create("duke");
System.out.println("Adapted id = " + adapted.getId());
} catch(Exception e) { ... }
Notice that the client code uses the standard EJB 2.x client programming model. It does not need to know that the target bean is implemented with EJB 3.0.
Running the Sample Code
A sample package accompanies this tip. To install and run the sample:
- If you haven't already done so, download the Java EE 5 SDK from the Java EE Downloads Page. Also be sure to have an installed version of the Java Platform Standard Edition (Java SE) 5 SDK.
- Download the sample package for the tip and extract its contents. You should now see the newly extracted directory as
<sample_install_dir>/ttfeb2007ejbmigration
, where <sample_install_dir>
is the directory where you installed the sample package. For example, if you extracted the contents to C:\
on a Windows machine, then your newly created directory should be at C:\ttfeb2007ejbmigration
. Below this directory you should see two subdirectories: Migration
and Adapted
. - To build and run the EJB 3.0-to-EJB 2.x compatibility example, change to the
Migration
directory. To build and run the EJB 2.x-to-EJB 3.0 compatibility example, change to the Adapted
directory. Then set the javaee.home
location attribute in the build.xml
file in the selected directory to the directory in which you installed the Java EE 5 application server. - Start the application server by entering the following command:
<appserv_install>/bin/asadmin start-domain domain1
where <appsrv_install>
is where you installed the Java EE 5 application server. - Enter the command:
ant all
This builds the sample application then deploys and executes it.
You should see the following in the output for the EJB 3.0-to-EJB 2.x compatibility example :
runappclient:
[exec] Successfully called EJB 3.0 bean
You should see the following in the output for the EJB 2.x-to-EJB 3.0 compatibility example:
runjavaclient:
[java] Adapted id = duke
The application is then automatically undeployed and its build resources erased.
Summary
The EJB 3.0 specification greatly simplifies the session bean API. But it does so with the understanding that there is a large investment in EJB applications built with earlier versions of the specification. For this reason, a goal of EJB 3.0 is to ensure smooth interoperability between new and existing applications. If you are a developer using EJB 3.0, you have a lot flexibility in deciding whether to migrate legacy applications to the new API or to take advantage of those legacy applications from new components.
About the Author
Ken Saks is the lead architect for the EJB 3.0 container in the Java EE 5 SDK. He has been a member of the Enterprise Java group at Sun Microsystems since 1999.