Stateless Session EJBs
I've written before that EJBs are really nothing new. Yes, you have
to adhere to a published interface but we do that all the time by
implementing Serializable or Comparable. I've put together a number
of stateless session beans in the last year which interface with a
back-end database. Why the customer wanted stateless session rather than entity
beans is not for me to question; I'm sure they had their justifications.
So how do you go about creating stateless session beans?
There are only three files which need to be generated:
- the bean itself
- the Home interface (containing a single method prototype)
- the Remote interface, containing prototypes for methods with
external visibility
I've written a number of tools which permit me to generate code
from a variety of sources. The base code was written separately
and tested extensively against multiple databases. The actual
input to the generator is a flat file, tab-separated, containing
information about the database table. I also have a tool which
takes an SQL table create file and generates the flat file. For the
sake of example, let's take a really simple table and follow the
process through.
Here's the SQL for creating a table with only two fields, one of
which is the primary key:
CREATE TABLE authentication {
username VARCHAR(32) NOT NULL,
password VARCHAR(16),
PRIMARY KEY( username )
};
|
We have to support the typical database operations such as insert,
delete and select. Since these are going to be stateless beans,
we cannot retain information between method calls; all necessary
information must be provided in each call. So here are the
prototypes of the methods we'll provide:
public void create( String username, String password ) throws RemoteException, SQLException;
public void delete( String username ) throws RemoteException, SQLException;
public String list() throws RemoteException, SQLException;
public void getPassword( String username ) throws RemoteException, SQLException;
public void setPassword( String username, String password ) throws RemoteException, SQLException;
|
All methods have to be defined as throwing the RemoteException and,
since I'm using JDBC, I also specify that an SQLException can also be
thrown. And that's the gist of the remote file! Add the imports and
the appropriate framework and you end up with the completed file:
import java.sql.SQLException;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface Authentication extends EJBObject {
// method prototypes from above
}
|
The Home interface is even simpler:
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import java.rmi.RemoteException;
public interface AuthenticationHome extends EJBHome {
public Authentication create() throws RemoteException, CreateException;
}
|
Since we're creating stateless session beans, there is no argument
to the constructor. Note that the create method returns an object
which implements the remote interface, i.e. Authentication rather
than AuthenticationHome. Now all we have to do is write the bean
methods themselves. Here is some sanitized (no JDBC included) code:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class AuthenticationBean implements SessionBean {
public SessionContext context = null;
public void create( String username, String password ) throws SQLException {
// insert a new record into the database
}
public void delete( String username ) throws SQLException {
// delete a record from the database
}
public String list() throws SQLException {
// return a space-separated list of primary keys
}
public String getPassword( String username ) throws SQLException {
// select a record from the database and return password field
}
public void setPassword( String username, String password ) throws SQLException {
// update a record in the database with a new password
}
public void setSessionContext( SessionContext context ) {
this.context = context;
}
public void ejbCreate() {
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
}
|
Those last five methods are required but don't have to do anything
in stateless session beans. You can put class initialization code in the
ejbCreate method if you desire, perhaps expensive operations such as
name lookups. You could even obtain a database connection, although I
prefer to do that in the individual methods and then explicitly release
it before returning. Just a personal preference.
Now that we've got the code written, how do we tie it all together?
My tools automatically generate the primary file required by J2EE
servers, namely ejb-jar.xml. This file must reside in the META-INF
directory of the generated jar file. Here's what the file would look
like in our example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar id="ejb-jar_ID">
<enterprise-beans>
<session id="Session_1">
<ejb-name>Authentication</ejb-name>
<home>AuthenticationHome</home>
<remote>Authentication</remote>
<ejb-class>AuthenticationBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
</assembly-descriptor>
</ejb-jar>
|
Now it should be noted that this file is typically generated by the
application builder component of the J2EE server. I examined the
files generated by my servers in order to determine the appropriate
format. It's also why I use the IBM mechanism of naming the session
beans sequentually in the form Session_#. So the ejb-jar.xml
file ties the three files together and gives them a name. But that's
only one part of the puzzle.
In order to deploy applications, more information is required, in
particular the mapping to JNDI (Java Naming and Directory Interface.)
Again, the tools supplied with the application servers can walk you
through creating the appropriate mappings but I found that the manual
method is just too time-consuming. So I once again resorted to looking
at the files created by WebSphere and WebLogic in order to be able to
generate them automatically.
IBM requires two extra XML files: ibm-ejb-jar-bnd.xmi (the bindings
file) and ibm-ejb-jar-ext.xmi (extensions.) Here's the ibm-ejb-jar-bnd.xmi
file for our example:
<ejbbnd:EJBJarBinding xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ejbbnd="ejbbnd.xmi" xmlns:ejb="ejb.xmi" xmi:id="ejb-jar_ID_Bnd">
<ejbJar href="META-INF/ejb-jar.xml#ejb-jar_ID"/>
<ejbBindings xmi:id="Session_1_Bnd" jndiName="ejbs/Authentication">
<enterpriseBean xmi:type="ejb:Session" href="META-INF/ejb-jar.xml#Session_1"/>
</ejbBindings>
</ejbbnd:EJBJarBinding>
|
And here's the ibm-ejb-jar-ext.xmi file:
<ejbext:EJBJarExtension xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ejbext="ejbext.xmi" xmlns:ejb="ejb.xmi" xmi:id="ejb-jar_ID_Ext">
<ejbJar href="META-INF/ejb-jar.xml#ejb-jar_ID"/>
<ejbExtensions xmi:type="ejbext:SessionExtension" xmi:id="Session_1_Ext" timeout="600">
<enterpriseBean xmi:type="ejb:Session" href="META-INF/ejb-jar.xml#Session_1"/>
<structure xmi:id="BeanStructure_1" inheritenceRoot="false"/>
<beanCache xmi:id="BeanCache_1" activateAt="ONCE"/>
<internationalization xmi:id="BeanInternationalization_1" invocationLocale="CALLER"/>
<localTran xmi:id="LocalTran_1" boundary="BEAN_METHOD" unresolvedAction="ROLLBACK"/>
</ejbExtensions>
</ejbext:EJBJarExtension>
|
It's not really necessary for me to explain these files: the meaning
should be fairly self-evident. The important element is the ejbBindings
and the jndiName parameter. Of course, WebLogic uses a completely
different format, with all necessary deployment information stored in
a file named weblogic-ejb-jar.xml. Here are the contents for our
example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EN"
"http://www.bea.com/servers/wls510/dtd/weblogic-ejb-jar.dtd">
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>Authentication</ejb-name>
<caching-descriptor>
<max-beans-in-free-pool>200</max-beans-in-free-pool>
<initial-beans-in-free-pool>10</initial-beans-in-free-pool>
</caching-descriptor>
<jndi-name>ejbs/Authentication</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
|
Again, nothing particularly surprising here, just some deployment parameters
and the
all-important jndi-name element. Now it's just a matter of actually
deploying the beans, a process which varies by server. Once you find
where your server places the files you can usually just copy them over
directly, especially when you're changing things frequently while developing
your applications. I generally restart the server once I've copied new
releases into the appropriate place. While WebSphere is good at recognizing
when a JSP has been changed and will recompile it, I'm not so sure that
it keeps track of timestamps on jar files.
So now that the EJBs are deployed you will want to do some local testing.
No sense having your JSP developers trying to use your beans unless you
know that they're functioning perfectly. The only difference in the code
used for testing versus the beans themselves is in the lookups. The beans
need only instantiate a new InitialContext and cast the results from the
lookup method as necessary. A client has to do a bit more work, including
using PortableRemoteObject.narrow to convert the result to the appropriate
class. Here's the bean code:
InitialContext ic = new InitialContext();
AuthenticationHome home = (AuthenticationHome) ic.lookup( "ejbs/Authentication" );
Authentication bean = home.create();
|
Here's the equivalent client code:
String nsFactory = null;
String urlString = null;
// if using WebLogic
nsName = "weblogic.jndi.WLInitialContextFactory";
urlString = "t3://localhost:7001";
// if using WebSphere
nsName = "com.ibm.websphere.naming.WsnInitialContextFactory";
urlString = "iiop://localhost";
Properties props = new Properties();
props.put( Context.INITIAL_CONTEXT_FACTORY, nsFactory );
props.put( Context.PROVIDER_URL, urlString );
Context context = new InitialContext( props );
Object obj = context.lookup( "ejbs/Authentication" );
AuthenticationHome home = (AuthenticationHome)
javax.rmi.PortableRemoteObject.narrow( obj,
AuthenticationHome.class );
Authentication bean = home.create();
|
It's interesting to note that WebLogic uses a proprietary protocol
for access to JNDI (t3) while IBM uses iiop (Internet Inter-ORB
Protocol.) Then again, BEA was in the application server business
very early on. In either case, we create the InitialContext using
the appropriate parameters and use PortableRemoteObject.narrow to
convert the object returned by the lookup.
Now it's just a matter of making the method calls. To change the
password of user 'joeblow' to 'password', for example, we'd only
need the following line:
bean.setPassword( "joeblow", "password" );
|
As you can see, accessing EJBs from the client is very straightforward.
See my article on JSPs for some demonstration
code which accesses EJBs.
Copyright © 2002 by Phil Selby