Make an EJB from any Java class with Java Reflection

Make any single-tier Java application into an enterprise application

Every EJB application server allows you to install an EJB if you provide a bean implementation, remote interface, and a home interface but, in most cases, you have to write them yourself. That is unavoidable and worth the effort for brand-new EJBs. But what if you have an existing class that performs the tasks you want your Enterprise Bean to do? Wouldn't it be nice to say, "Make me an EJB that does that?" Taking the idea further, what if you use those classes within existing applications? Wouldn't it be nice if the applications could be modified easily to use your new EJBs without having to manually replace every class instantiation with a JNDI lookup followed by a HomeInterface.create()?

The idea first occurred to me near the end of 1999, while working with the ObjectSpace Voyager Application Server, and I have subsequently applied the idea with Oracle Application Server. Next, I will turn my attention to the J2EE reference implementation.

What's the big idea?

For any EJB that you wish to install in an application server, you must supply three Java classes: a bean implementation (for example, BankBean.java), a home interface (for example, BankHome.java) and a remote interface (for example, BankRemote.java). The bean implementation provides the functionality, the home interface allows you to lookup and create new bean instances, and the remote interface allows you to communicate with the bean. That is all shown for an EJB called BankBean in Figure 1.

Figure 1. BankBean implementation, home interface, and remote interface

So for a new EJB, you need three Java classes, each of which must adhere to the EJB specification. For just one new EJB, creating three classes -- all EJB compliant -- might not be so bad. But if you're creating lots of EJBs -- perhaps from functionality you've already coded into existing classes -- a more automated solution would be beneficial.

The basis of my idea is, therefore, to produce an EJBWizard utility that will take any given Java class and generate all of the additional support classes that will allow it to be used remotely as an EJB. I have set myself the additional requirement that the original class should be left unchanged. That will allow the class to be used locally as it is now and will enable functional changes to be made more easily in the future. That means that the bean implementation that I will generate will be a wrapper, which delegates method calls to the original class, instead of a modified version of the class itself. You will see what that looks like in Figure 2.

Figure 2. BankBean implementation delegating to Bank

Thus, existing applications will be completely unaffected in their use of the original class, whereas new applications that will use the class' EJB version will now have to use JNDI to look up the home interface and then call create() on the home interface to access an EJB instance through the remote interface. To make it easier to write new client applications and to facilitate the conversion of existing applications to use the distributed versions of their classes, I will add a client-side factory that hides the details of the JNDI lookup and creation via the home interface, as seen in Figure 3.

Figure 3. Addition of the client-side BankFactory

How will it work?

When interviewing potential employees, a former colleague said he would ask them, "Can you give me an example of a use for Java Reflection?" The implication was that few situations require the use of Reflection and, in most cases, a better technique can be used. I never quite believed that, so here is my one good use for Reflection.

I will use Reflection to analyze an original Java class given as input to find out which method signatures the class provides. That information will be used to generate a remote interface and a (delegating) bean implementation class, both of which support the same set of methods. The constructor method signatures will be used to generate a home interface that provides equivalent create methods.

The EJBWizard class is the entry point in my design. It has a constructor that takes the original Java class, that is, the one that will become an EJB:

public EJBWizard(Class originalClass)

The EJBWizard also has a set of methods, each dedicated to generating one aspect of the EJB:

  • public String getRemote()
  • public String getHome()
  • public String getBean()

Each method returns a string containing the complete source code for the home interface, remote interface, or (delegating) bean instance as appropriate, based on the original Java class. Within each method, reflection discovers the constructor and method signatures of the original class, and those are then written out in modified form as new Java source code. Let's take each method in turn.

Generating the remote interface with getRemote()

The code for the getRemote() method shows the general principle upon which the other methods will be based:

public String getRemote()
{
  String codeString="";
  // -- generate package name if there is one --
  if (!this.introspector.getPackage().equals(""))
    codeString=codeString+"package
"+this.introspector.getPackage()+";\n\n";
  // -- ejb import statement --
  codeString=codeString+"import javax.ejb.*;\n";
  // -- interface declaration --
  codeString=codeString+"\npublic interface ";      
  codeString=codeString+this.introspector.getShortName()+ "Remote
extends javax.ejb.EJBObject";
  codeString=codeString+"\n";
  codeString=codeString+"{\n";
  // -- public methods --
  String[] methods=this.introspector.getMethods();
  for (int i=0; i<methods.length; i++)
  {
    if (methods[i].indexOf("throws")>=0)
    {
      codeString=codeString+"  "+methods[i]+",
java.rmi.RemoteException;\n";
    }
    else
    {
      codeString=codeString+"  "+methods[i]+" throws
java.rmi.RemoteException;\n";
    }
  }
        
  codeString=codeString+"}\n";
  return codeString;
}

The code above shows how the source code for a new remote interface -- comprising a package statement, EJB import statement, interface declaration, and a set of methods signatures -- is written into an output string based on the information provided by an introspector instance. The introspector is an instance of ClassIntrospector whose job is to hide the details of how Java Reflection is applied to the original class supplied as input. We'll see the inner workings of ClassIntrospector later but, for now, I don't want to lose the plot line.

Given a simple Java class:

public class Bank
{
  public Bank(String bankName)
  { // -- construct the bank --
  }
  public String createAccount(int balance)
  { // -- create an account and return accno --
    return "00000"; }
  public boolean transfer(String acc1, String acc2, int amount)
  { // -- transfer an amount between accounts --
    return true; }
}

the getRemote() method would generate the following code for the remote interface:

import javax.ejb.*;
public interface BankRemote extends javax.ejb.EJBObject
{
  public java.lang.String createAccount(int p0) throws
java.rmi.RemoteException;
  public boolean transfer(java.lang.String p0,java.lang.String p1,int
p2) throws java.rmi.RemoteException;
}

That's it for the remote interface. Next, we see something similar for the home interface.

Generating the home interface with getHome()

The getHome() method follows the same pattern as the getRemote() method, with just a couple of changes. First, the interface declaration is changed to that of a home interface:

codeString=codeString+"\npublic interface ";
codeString=codeString+this.introspector.getShortName()+ "Home extends
javax.ejb.EJBHome";

Second, the original class methods are ignored, but the constructors are used to generate one or more create methods:

String[] constructors=this.introspector.getConstructors();
for (int i=0; i<constructors.length; i++)
{
  codeString=codeString+"  public "+this.introspector.getName()+"Remote
create"+constructors[i]+" throws javax.ejb.CreateException,
java.rmi.RemoteException;\n";
}

Thus, the source code for the home interface generated for the example Bank class is:

import javax.ejb.*;
public interface BankHome extends javax.ejb.EJBHome
{
  public BankRemote create(String p0) throws javax.ejb.CreateException,
java.rmi.RemoteException;
}

When I applied that technique with Oracle Application Server, I discovered that it allows only one create method per home interface because the EJB implementation sits on top of a CORBA ORB that does not allow methods to be overloaded with differing signatures. The create methods that I generate are meant to mirror the original class' constructors, which may be overloaded, so that was a significant limitation. My solution was to separate the creation of an instance (via a single, parameterless, create method on the home interface) from the initialization of an instance (via one or more initialization methods on the bean itself), each doing one of the constructors' jobs. Next up: the bean implementation.

Generating the bean implementation

Generation of the bean implementation combines aspects of the getRemote() and getHome() implementations. The original class' constructors become ejbCreate() methods that correspond with the create methods on the home interface, and the original class' method signatures are used to generate methods that delegate to an instance of the original class (which has been left intact throughout).

So for the constructors we have:

String[] constructors=this.introspector.getConstructors();
String[] constructorCalls=this.introspector.getConstructorCalls();
for (int i=0; i<constructors.length; i++)
{
  codeString=codeString+"  public "+this.introspector.getName()+"Remote
ejbCreate"
    +constructors[i]+" throws javax.ejb.CreateException\n";
  codeString=codeString+"  {\n";
  codeString=codeString+"    realInstance=new "+this.introspector.getName()+constructorCalls[i]+";\n";
  codeString=codeString+"  }\n";
}

You will see here that within each ejbCreate() method of the bean implementation, we create an instance of the original class, named realInstance(). All calls to that bean implementation will be delegated to the realInstance().

Below you'll find the code that generates the methods on the bean implementation, which will delegate to the realInstance:

String[] methodCalls=this.introspector.getMethodCalls("realInstance",true);
String[] methods=this.introspector.getMethods();
for (int i=0; i<methods.length; i++)
{
  codeString=codeString+"  "+methods[i]+"\n";
  codeString=codeString+"  {\n";
  codeString=codeString+"    "+methodCalls[i]+"\n";
  codeString=codeString+"  }\n";
}

Note in the code above that when asking the introspector for the original class's method calls, I pass the text "realInstance" to be prepended to each method call.

Now that the code for the constructors and business methods has been generated, some EJB methods must be added to make that a valid bean implementation. For simplicity, I will use the following code to generate empty methods:

codeString=codeString+"  public void setSessionContext(SessionContext
ctx)\n";
codeString=codeString+"  { this.sessionContext=ctx; }\n";
codeString=codeString+"  public void ejbActivate() {}\n";
codeString=codeString+"  public void ejbPassivate() {}\n";
codeString=codeString+"  public void ejbRemove() {}\n";

The final generated code for the bean implementation, to support the original Bank class, will look like this:

public class BankBean implements javax.ejb.SessionBean
{
  private Bank realInstance;
  private SessionContext sessionContext;
  // -- business methods --
  public java.lang.String createAccount(int p0)
  {
    java.lang.String returnValue=realInstance.createAccount( p0);
return returnValue;
  }
  public boolean transfer(java.lang.String p0,java.lang.String p1,int
p2)
  {
    boolean returnValue=realInstance.transfer( p0, p1, p2); return
returnValue;
  }
  // -- ejb create --
  public void ejbCreate(java.lang.String p0) throws
javax.ejb.CreateException
  {
    realInstance=new Bank( p0);
  }
  // -- ejb methods --
  public void setSessionContext(SessionContext ctx) {
this.sessionContext=ctx; }
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void ejbRemove() {}
}

The code to generate each aspect of an EJB from any supplied class has been kept simple, thanks to the elusive introspector, which comes up next.

Inside the introspector

To get across the main points about how to generate the new EJB's Java code, I have so far ducked the issue of how to analyze the original Java class using Reflection. Remember that we have used an extra object called introspector in the examples to hide the details of how reflection is used. The introspector object is an instance of a class called ClassIntropector, which we will now crack open to take a look inside.

When creating a new instance of ClassInstropector, we pass the original Java class -- something like ClassIntrospector introspector= new ClassIntrospector(Class.forName("Bank")); -- which calls the constructor:

public ClassIntrospector(java.lang.Class sourceClass)
{ this.sourceClass=sourceClass; }

ClassIntrospector has a set of methods to tell about the methods and constructors declared on the original class:

public String[] getMethods()
public String[] getMethodCalls(String callInstance, boolean returnFlag)
public String[] getConstructors()
public String[] getConstructorCalls()

A call to getMethods() returns the original class' methods in a form suitable for declaring a new class with the same methods. It starts by making a reflection call:

Method[] methods=sourceClass.getMethods();

It then iterates through the array of methods and for each one obtains the return type, method name, parameter types, and exceptions by using the following invocations:

String returnType=thisMethod.getReturnType().getName();
String methodName=thisMethod.getName();
Class[] parameters=thisMethod.getParameterTypes();
Class[] exceptions=thisMethod.getExceptionTypes();

I use all of that information to build an output string of the form:

public java.lang.String createAccount(int p0) throws SomeException;

That's fine for defining methods on the new EJB that reflect (no pun intended) the original Java class' methods, but we also need to generate methods in a form to delegate calls to the realInstance. For the latter, I invoke getMethodCalls("realInstance",true), which performs almost identical work to getMethods() but generates code in the form:

public java.lang.String createAccount(int p0) throws SomeException
{
  java.lang.String returnValue=realInstance.createAccount( p0); return
returnValue;
}

The getConstructors() and getConstructorCalls() methods are nearly the same as their method counterparts, except they generate EJB create methods based on the original class' constructors.

So what about complete applications?

The introduction stated that the idea presented above could be extended beyond simply generating the EJB support classes for any given Java class -- extended to allow applications that use the original class to be converted to use the new EJB. A significant amount of work needs to be done to convert every instantiation of the form Bank aBank=new Bank(1000); to the EJB form (for Oracle Application Server):

java.util.Hashtable env = new java.util.Hashtable();
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,"oracle.oas.namin
.jndi.RemoteInitCtxFactory");
javax.naming.Context initialContext = new
javax.naming.InitialContext(env);
newInstanceHome=(BankHome) oracle.oas.eco.PortableRemoteObject.narrow
  (initialContext.lookup(lookupURL+"/Bank"),BankHome.class);
BankRemote newInstanceRemote=newInstanceHome.create(1000);

To simplify, I include an additional method on the EJBWizard, called getFactory(), which generates a client-side factory class that hides the JNDI lookup and creation via the home interface. That allows client applications to be converted by replacing (local) instantiation of the form Bank aBank=new Bank(1000); with (remote) instantiation of the form BankRemote aBank=bankFactory.newInstance(1000);.

As long as all instantiations of the old form are replaced by instantiations of the new form, the client application should work with the (remote) EJB version of the class as well as it did with the original (local) version of the class, subject to certain limitations, as seen next.

Limitations of the solution

I have made no mention of Entity Beans, and the EJBWizard limits itself to the conversion of Java classes to stateful session beans because any client application employing a local class is assured that each instance of the class is a separate instance, with its own set of member variables preserved between invocations. When the local class is converted to an Enterprise Bean, only a stateful session bean provides the same assurance.

Conclusion

I have used the technique presented in this article in real life, in a slightly modified form for each new project and each new application server. At best, it facilitates the EJB-enabling of whole applications; at worst, it is a useful way to automate the routine aspects (home interface, remote interface, and so on) of EJB creation. Much work remains to be done, including development in the area of static class methods that are currently not supported. Any ideas?

You might have seen an implementation of that idea inside your Java IDE. I think Oracle's JDeveloper has some kind of wizard to convert a standard class into an EJB, and IBM's VisualAge may have one, too. If you're not lucky enough to have an IDE that does that for you or if it doesn't support the EJB flavor for your application server, you'll have to implement it yourself. Now you know how.

Tony Loton is an independent consultant, course instructor, and technical author who works through his company, LOTONtech. He has been working with Java, CORBA, and Unified Modeling Language for several years, and has presented distributed computing seminars nationally and internationally.

Learn more about this topic

  • JavaWorld resources
  • Other important server-side resources

Join the discussion
Be the first to comment on this article. Our Commenting Policies