Java Tip 118: Utilize the EjbProxy
Invoke remote EJBs with a simple utility class
If you’ve ever worked with Enterprise JavaBeans (EJB), you’ve probably written code to access the EJB from the client program or written code to access it from another EJB. Usually, those code implementations are similar to each other with just a few changes. Good programmers manage to copy and paste most of it (as a kind of code reuse); however, after copying and pasting over the same code several times, it becomes boring. That’s why I decided to implement a class that does it for me, called EjbProxy
. To keep things clean and simple, this class invokes any EJB without knowing anything more than the EJB’s JNDI (Java Naming and Directory Interface) lookup name, and you can use it on the client side or in an EJB.
You must follow four basic steps to invoke an EJB:
- Create a JNDI initial context
- Look up the EJB name in the context
- Get the
home
object - Create the EJB object’s instance via the
home
object’screate()
method
The implementation for Steps 1 through 3 is trivial:
InitialContext ctx = new InitialContext (prop);
Object home = ctx.lookup(beanName);
EJBHome obHome =
(EJBHome) PortableRemoteObject.narrow (home, EJBHome.class);
As you can see, you can obtain the EJB home
object without knowing the specific implementation of that particular EJB, except the JNDI name lookup string.
Step 4 is not so straightforward. When you try to create the EJB object using the home
object, you can’t invoke the create()
method from the home
object, since the EJBHome
interface doesn’t define the create()
method. Only the actual EJB implementations define and implement this method.
As one possible solution, you could define an interface that extends EJBHome
and declares the create()
method. Then you could simply cast the home
object to this interface and call the create()
method. However, this approach has one drawback: it requires all EJBs to implement this particular interface. You’d want to invoke the create()
method of an arbitrary home interface.
A better solution would use Java’s Reflection API capabilities. (You can refer to Resources for detailed information on the Reflection API.) By using reflection, you can load the specified class using Class.forName
, and then call getDeclaredMethod()
to retrieve the method the class declares. Once you obtain the Method
object, you can execute the method with Method.invoke
.
The following code uses reflection to create the EJB object’s instance from its home
object:
Method m = obHome.getClass().getDeclaredMethod("create", new Class[0]);
Object objRemote = m.invoke (obHome, new Object[0]);
Object obj = PortableRemoteObject.narrow(objRemote, theClass);
That is, this code segment gets the class from the home
object, and then calls getDeclaredMethod()
with the create()
method’s name in order to retrieve create
‘s Method
object. It also uses the invoke()
method to execute the create()
method, which returns the EJB object’s instance.
The EjbProxy class
The EjbProxy
class, found in the source code’s EjbProxy.java, manages these nasty details for you.
Using EjbProxy
is fairly simple. First, you construct an EjbProxy
object — you may pass the parameters, such as the initial context factory and the provider URL for creating the initial context. Then you call the getObj()
method with the EJB’s JNDI lookup name to get the EJB’s instance. Note that if you use the reference name when invoking another EJB from an EJB, you may need to add a lookup environment string to the lookup name, such as java.comp/env/
.
Sample code in the download shows you how to use the EjbProxy
class to instantiate and invoke an EJB. For testing purposes, I also implemented an EJB called EjbProxyExample
, which has one public method:
public String hello (String name) throws RemoteException;
This method simply accepts a name string and returns a string that makes up “Hello,” and the name string. The JNDI name for the EJB proxy example is com.javaworldtip.ejbproxyexample.EjbProxyExample
. Suppose you use WebLogic to build and deploy this sample EJB on the local machine with port 7001; to instantiate EjbProxyExample
, the client code might be:
EjbProxy myproxy = new EjbProxy ("weblogic.jndi.WLInitialContextFactory",
"t3://localhost:7001");
EjbProxyExample obj = (EjbProxyExample)myproxy.getObj
("com.javaworldtip.ejbproxyexample.EjbProxyExample");
//now get "Hello, World!" string and show it
String strHello = obj.hello("World");
System.out.println (strHello);
If you invoke this EJB from another EJB located inside the same server, then the code looks like:
EjbProxy myproxy = new EbjProxy ();
EjbProxyExample obj = (EjbProxyExample)myproxy.getObj
("com.javaworldtip.ejbproxyexample.EjbProxyExample ");
If you’ve already specified the EjbProxyExample
in the ejb- ref
section of your caller EJB with reference name "EjbProxyExample"
, then you can directly use the reference name in the getObj()
method:
EjbProxyExample obj =
(EjbProxyExample) myproxy.getObj ("java:comp/env/EjbProxyExmaple");
How it works
You can use the extended constructor or the setContextProperties()
method to set up any context property that EJBProxy
must use:
public void setContextProperties (String initContextFactory,
String providerUrl,
String user,
String password)
{
_prop = new Properties ();
_prop.put (Context.INITIAL_CONTEXT_FACTORY, initContextFactory);
_prop.put (Context.PROVIDER_URL, providerUrl);
if (user != null)
{
_prop.put(Context.SECURITY_PRINCIPAL, user);
if (password == null)
password = "";
_prop.put(Context.SECURITY_CREDENTIALS, password);
}
}
Here all parameters are the environment values for creating the initial context, which include the initial context factory, provider URL, user ID, and password. If you use the class to retrieve the EJB in the same container, you can simply pass null or ignore them.
Now, let’s look at the most important method in our proxy class, getObj():
public Object getObj (String beanJndiLookupName) throws EJBException
{
try
{
EJBHome obHome = getHome (beanJndiLookupName);
//get the method of create
Method m = obHome.getClass().getDeclaredMethod("create", new Class[0]);
//invoke the create method
Object obj = m.invoke (obHome, new Object[0]);
return obj;
}
catch (NoSuchMethodException ne)
{
throw new EJBException (ne);
}
catch (InvocationTargetException ie)
{
throw new EJBException (ie);
}
catch (IllegalAccessException iae)
{
throw new EJBException (iae);
}
}
In getObj()
, the only required parameter is the JNDI name of the EJB you are looking for. The method first calls the getHome()
method to obtain the EJBHome
object, then calls getDeclaredMethod()
to retrieve the create()
method via the EBJHome
‘s Class
reference. Once the create()
method is captured, you can invoke it to get the EJB object’s instance. If anything goes wrong, the invocation throws an exception.
You must handle a few possible exceptions. For example, if the EJB does not declare the create()
method, the method getDeclaredMethod()
throws a NoSuchMethodException
. In this code, the various catch clauses handle these possible exceptions by throwing a new EJBException
. This way, the code calling the getObj()
method must catch the EJBException
and handle this exception properly.
Following is the getHome()
method code that the getObj()
method calls:
public EJBHome getHome (String beanJndiLookupName) throws EJBException
{
try
{
InitialContext ctx = null;
if (_prop != null)
ctx = new InitialContext (_prop);
else
ctx = new InitialContext ();
Object home = ctx.lookup(beanJndiLookupName);
EJBHome obHome = (EJBHome)PortableRemoteObject.narrow (home, EJBHome.class);
return obHome;
}
catch (NamingException ne)
{
throw new EJBException (ne);
}
}
This method simply wraps Steps 1 through 3 for invoking the EJB as mentioned above. I also make getHome()
a public method just in case the home
object is desirable; i.e., you might want to hold a reference to the EJBHome
object, and use it to call its methods again later (such as calling the create()
method to create a new session bean).
Extend EjbProxy
For some situations, you might want to extend the EjbProxy
class. For example, the EJB’s create()
method is assumed without any input parameters. You can easily extend the class to handle more generic situations with the revised getObj()
method:
public Object getObj (String beanJNDILookupName, Class[] classes, Object[]
objects) throws EJBException;
Inside the method, apply the additional parameters when calling getDeclaredMethod()
and invoke()
methods:
Method m = obHome.getClass().getDeclaredMethod("create", classes);
Object obj = m.invoke (obHome, objects);
You can also extend this class to execute an EJB’s arbitrary method by applying reflection:
public Object executeMethod (String methodName, Class[] classes, Object[]
objects) throws EJBException;
Here methodName()
is the name of the method to execute. You may need to cache the EJB object in the class after obtaining the EJBHome
object, then use the similar process used in the revised getObj()
method to invoke the method.
Simplify your everyday coding
By using Java’s Reflection mechanism, you can use a generic and flexible EJB proxy class, which lets you invoke EJBs either from a remote client or from another EJB. As you can see, you could use the EjbProxy
class or extend it to simplify your everyday coding associated with instantiating and invoking EJBs.