- EJB 3 Developer Guide
- Michael Sikora
- 3486字
- 2021-07-02 11:34:54
Stateless Session Beans
A stateless session bean's state spans a single method call. We cannot have method A
updating an instance variable x
say, and expect the updated value of x
to be available for any subsequent method invocation. This holds true both for a new method B
which accesses x
, or indeed, if method A
is invoked a second or subsequent time. We have no guarantee that the same stateless session bean instance will be invoked between method calls. For this reason, stateless session beans should not use instance variables to maintain conversational state.
As a simple example we will develop a stateless session bean which simply returns the current time to the client. This is almost as minimal as the traditional HelloWorld example but is more useful. Recall a session bean component consists of a bean interface and a bean class. We name our interface TimeService.java
, which contains a single method definition getTime()
:
package ejb30.session; import javax.ejb.Remote; @Remote public interface TimeService { public String getTime(); }
Note that we have prefixed the interface definition with the @Remote
annotation. This indicates to the EJB container that this bean may be invoked by a remote client. By remote, we mean a client that does not reside in the same Java Virtual Machine (JVM) as the container. An interface can also be local as we shall see later in this chapter.
The @Remote
annotation belongs to the javax.ejb
package, as we can see from the import
statement. Annotations belong to a number of packages. We list annotations and their corresponding packages in Appendix A.
Note that we have decided to place our code in the ejb30.session
package. The interface is implemented by the EJB container, and not the application, when the bean is deployed to the container.
Next we code the bean class itself, TimeServiceBean.java
:
package ejb30.session; import java.util.*; import javax.ejb.Stateless; @Stateless public class TimeServiceBean implements TimeService { public String getTime() { Formatter fmt = new Formatter(); Calendar cal = Calendar.getInstance(); fmt.format("%tr", cal); return fmt.toString(); } }
This class contains the implementation of the getTime()
business method. This looks like any Java class, all we have added is the @Stateless
annotation. This indicates to the EJB container that the session bean is stateless.
Annotations
The option of annotations is one of the most important features which distinguishes EJB 3 from earlier versions. Metadata annotations were introduced in Java SE 5. For this reason Java SE 5 or higher must be used in conjunction with EJB 3.
In EJB 2.x, instead of annotations we use XML deployment descriptors. As the name suggests, deployment descriptors are read by the application server when the EJB is deployed. We will discuss the deployment process later in this chapter. The deployment descriptor, ejb-jar.xml
, for the above session bean is shown below:
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" metadata-complete="true" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ ejb-jar_3_0.xsd"> <enterprise-beans> <session> <display-name>TimeServiceBean</display-name> <ejb-name>TimeServiceBean</ejb-name> <business-remote>ejb30.session.TimeService</business-remote> <ejb-class>ejb30.session.TimeServiceBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <security-identity> <use-caller-identity/> </security-identity> </session> </enterprise-beans> </ejb-jar>
Note the lines:
<business-remote>ejb30.session.TimeService</business-remote>
and
<session-type>Stateless</session-type>
indicate that we have a remote stateless session bean. All other lines correspond to default values for an annotated session bean.
Annotations are far simpler to use than deployment descriptors. Deployment descriptors tend to be verbose, not very readable by humans and error prone when manually edited. Any non-trivial deployment descriptor file really needs a tool both for writing and reading. However annotations have been criticized on the grounds that deployment aspects are tangential to the definition of a bean and should not intrude in the bean's code. The use of annotations means recompiling a bean when we only want to change a deployment aspect; modifying a deployment descriptor does not warrant recompilation. However annotations are optional in EJB 3. We can still use XML deployment descriptors if we wish. Furthermore we can mix both annotations and deployment descriptors in EJB 3. In such cases deployment descriptors always override annotations.
One approach might be to use annotations for those aspects of an EJB which rarely change between deployments. For example, the relationship of an entity which we will discuss in Chapter 4, is likely to be invariant. The same can be said for transaction attributes of a session bean, which we will discuss in Chapter 7. On the other hand database table names onto which entities are mapped (covered in Chapter 3) or security aspects (covered in Chapter 12), for example, are more likely to change between deployments. Consequently deployment descriptors are more appropriate here.
On large projects the teams or individuals responsible for deployment may not be the same as those responsible for development. It is unrealistic to expect the former to add or modify any annotations within code developed by others. In these circumstances deployment descriptors would be used.
However, because this is an introductory book and we want to keep its length reasonably short we will only make use of annotations in nearly all our examples.
Creating a Session Bean Client
Next we will create a client which is a standalone Java application. The client simply references a session bean, invokes the bean's getTime()
method, then prints out the result:
package ejb30.client; import javax.naming.*; import ejb30.session.*; public class Client { public static void main(String args[]) throws Exception { InitialContext ctx = new InitialContext(); TimeService timeService = (TimeService) ctx.lookup("ejb30.session.TimeService"); String time = timeService.getTime(); System.out.println("Time is: " + time); } }
Note that because the client is remote we need to use JNDI (Java Naming and Directory Interface) to lookup the session bean. All EJB containers implement JNDI. JNDI is a set of interfaces which has many implementations: RMI, LDAP, and CORBA amongst others. There are two steps for performing a JNDI lookup. The first step is to create a JNDI connection by creating a javax.naming.InitialContext
object. One way to do this is to create a java.util.Properties
object, set JNDI properties specific to the EJB container, then pass the properties object as an argument to InitialContext
. This will take the form:
Properties env = new Properties(); // Use env.put to set container specific JNDI properties InitialContext ctx = new InitialContext(env);
An alternative method is to define the properties in a jndi.properties
file and use the no-arg InitialContext
constructor:
InitialContext ctx = new InitialContext();
The JNDI properties are then accessed at runtime. The GlassFish container provides a default jndi.properties
file which is contained in appserv-rt.jar
. This JAR file must be in the client's classpath, as we shall shortly see when we examine the Ant build file.
The second step is to use the InitialContext
object to lookup the remote EJB. In our example this is done by the statement:
TimeService timeService = (TimeService) ctx.lookup("ejb30.session.TimeService");
Note the argument passed is the global JNDI name. This is generated by the container at deploy time. The form of the global JNDI name is also container-specific. In the case of GlassFish this is the fully qualified remote session bean interface name. In our example this is ejb30.session.TimeService
. The lookup result is directly cast to the session bean interface type, namely TimeService
.
We can now invoke the session bean business method:
String time = timeService.getTime();
The use of JNDI is one of the more awkward aspects of EJB technology. Fortunately in EJB 3 the only occasion its use is mandatory is when a client invokes EJB from outside the container. If we invoke an EJB from within a container, then we can use dependency injection annotation metadata instead of JNDI lookups. We will return to this shortly.
Running the Example
First we need to compile the source code. The Ant script is run from the command line, as follows:
cd C:\EJB3Chapter02\glassfish\lab1 C:\EJB3Chapter02\glassfish\lab1>ant compile
This will compile the interface, TimeService.java
, the session bean, TimeServiceBean.java
, and the client, Client.java
.
The next step is to package the session bean into an EJB module. An EJB module consists of one or more EJBs contained in a JAR file. In our example we have just one EJB, TimeService
, and we name the JAR file, TimeService.jar:
C:\EJB3Chapter02\glassfish\lab1>ant package-ejb
We can use the jar -tvf
command to examine the contents of the JAR file:
C:\EJB3Chapter02\glassfish\lab1\build>jar tvf TimeService.jar
...META-INF/
...META-INF/MANIFEST.MF
...ejb30/
...ejb30/session/
...ejb30/session/TimeService.class
.. ejb30/session/TimeServiceBean.class
Note the JAR file contains the standard META-INF
directory and the default manifest file, MANIFEST.MF
.
The next step is to deploy the program. We can do this within the GlassFish administrator console, which has an option for deploying an EJB module. Alternatively we can use an Ant script as follows:
C:\EJB3Chapter02\glassfish\lab1>ant deploy
Deployment is essentially the process by which applications are transferred to the control of the application server. The developer copies the EJB module to designated sub-directories within the application server directory structure. However, behind the scenes a lot more takes place during deployment. Typically some sort of verification takes places to check that the contents of the EJB module are well formed and comply with the Java EE specification. If annotations are used, the application server may generate corresponding XML deployment descriptor files. Additional vendor-specific XML files and classes and interfaces are typically generated. Entities will be mapped to a database and optionally corresponding database tables may be created during deployment. We will discuss entities and databases in Chapter 3. During deployment security roles are mapped to user groups. We will discuss security in Chapter 12.
Note that some application servers do not have the option of deploying EJB modules. Such modules need to be placed in an EAR (Enterprise Application Archive) file before deployment. We shall see an example of an EAR file shortly.
Finally we can run the example with the run-client Ant command:
C:\EJB3Chapter02\glassfish\lab1>ant run-client
Buildfile: build.xml
compile:
run-client:
[java] Time is: 03:21:17 PM
BUILD SUCCESSFUL
The Program Directory Structure
Before we describe the Ant build scripts in more detail we will look at the directory structure in which the source code is placed.

lab1: This is the root directory and contains the following files and subdirectories:
- build.xml: This is the Ant build script itself. We will describe it in more detail shortly.
- env.properties: This file contains runtime environment property settings and is read by the Ant build script. Currently the file contains just one value,
glassfish.home
, which is the root directory in which GlassFish is installed. When writing this book GlassFish was installed on Windows in theC:/glassfish
directory, so the contents ofenv.properties
is:glassfish.home=C:/glassfish
- adminpassword: This file contains the GlassFish administrator password required for the deployment task. This is identified by
AS_ADMIN_PASSWORD
. The initial password, which can be reset of course, isadminadmin
. So the contents ofadminpassword
are:AS_ADMIN_PASSWORD=adminadmin
- src: This subdirectory contains the source code. Recall the client program,
Client.java
, is placed in theejb30.client
package. The package name is reflected in the subdirectory structure. Subdirectories have also been created to correspond to theejb30.session
package in whichTimeService
andTimeServiceBean
have been placed. - build: This subdirectory contains files created during the build process. For example, class files created during compilation and JAR files created during packaging will be placed here.
The Ant Build Script
We use an Ant build file for automating the compilation of client and session bean source code as well as packaging and deploying the session bean. We also use the build file for running the client. All these steps are defined as Ant targets. The Ant build file is listed below:
<project name="ejb30notebook" basedir="."> <property name="build.dir" value="${basedir}/build" /> <property name="src.dir" value="${basedir}/src" /> <property file="env.properties" /> <path id="j2ee.classpath"> <pathelement location="${build.dir}"/> <fileset dir="${glassfish.home}/lib"> <include name="javaee.jar"/> <include name="appserv-rt.jar"/> </fileset> </path> <target name="clean"> <delete dir="${build.dir}"/> <mkdir dir="${build.dir}" /> <mkdir dir="${build.dir}/lib" /> </target> <target name="all"> <antcall target="clean"/> <antcall target="compile"/> <antcall target="package-ejb"/> <antcall target="deploy"/> <antcall target="run-client"/> </target> <target name="compile"> <javac destdir="${build.dir}" srcdir="${src.dir}" classpathref="j2ee.classpath"/> </target> <target name="package-ejb" depends="compile"> <jar jarfile="${build.dir}/TimeService.jar"> <fileset dir="${build.dir}"> <include name="ejb30/session/**" /> </fileset> </jar> </target> <target name="deploy"> <exec executable="${glassfish.home}/bin/asadmin" failonerror="true" vmlauncher="false"> <arg line="deploy --user admin --passwordfile adminpassword ${build.dir}/TimeService.jar"/> </exec> </target> <target name="undeploy"> <exec executable="${glassfish.home}/bin/asadmin" failonerror="true" vmlauncher="false"> <arg line="undeploy --user admin --passwordfile adminpassword TimeService"/> </exec> </target> <target name="run-client" depends="compile"> <java classname="ejb30.client.Client" fork="yes" classpathref="j2ee.classpath"/> </target> </project>
Within the path
tag we have the classpath, identified by j2ee.classpath
. This contains the javaee.jar
file (containing libraries such as javax.ejb
) required for compilation and appserv-rt.jar
required for runtime execution of a client.
The clean
target deletes and recreates the build
directory. This is used to ensure that all generated files such as class and JAR files are recreated from scratch.
The all
target performs all the tasks required to deploy and run the example.
The deploy
target uses the GlassFish asadmin
tool to deploy the TimeService.jar
file. The line:
<arg line="deploy --user admin --passwordfile adminpassword ${build.dir}/TimeService.jar"/>
specifies that deployment is performed by the admin
user. The admin
password is held in a file named adminpassword
.
There is also an undeploy
target, which removes TimeService.jar
from the application server.
The Application Client Container
Although accessing an EJB from a client using JNDI is simpler than in EJB 2.x, it is still rather awkward. The good news is that we can dispense with JNDI altogether if the client runs from within an application client container (ACC). The EJB 3 specification does not mandate that an EJB-compliant application server provides an ACC but makes its inclusion optional. Consequently not all EJB-compliant application servers provide an ACC, however GlassFish does.
An ACC enables a client executing in its own JVM outside the EJB container to access a number of Java EE services such as JMS resources, security, and metadata annotations.
If the previous client example is run from an ACC then we can use dependency injection annotation metadata instead of JNDI lookups. Here is the new version of Client
:
package ejb30.client; import javax.naming.*; import ejb30.session.*; import javax.ejb.EJB; public class Client { @EJB private static TimeService timeService; // injected field must be static public static void main(String args[]) throws Exception { String time = timeService.getTime(); System.out.println("Time is: " + time); } }
We use the @EJB
annotation to instruct the container to lookup the TimeService
bean and inject a bean reference into the timeService
field. This is an example of field injection. Another kind of dependency injection is setter injection; we shall see an example of this later in this chapter. We can then invoke any of the TimeService
methods such as timeService.getTime()
.
Because the Client
class runs in a static context, as a main method, the injected field must also be static.
We need to modify the build process if we are using an ACC. The build steps are application sever-specific so what follows in this section applies only to GlassFish. First we create a manifest.mf
file with the contents:
Main-Class: ejb30.client.Client
We need to do this because the client will be bundled in a JAR file, as we shall see shortly, and we need to indicate which class in the JAR file is the application's entry point. The compilation and EJB packaging tasks require no change. However in order to use the ACC we must package the client in a JAR file. The following Ant script creates a Client.jar
file.
<target name="package-client" depends="compile"> <jar jarfile="${build.dir}/Client.jar" manifest="${config.dir}/manifest.mf"> <fileset dir="${build.dir}"> <include name="ejb30/client/Client.class" /> </fileset> </jar> </target>
Note that we have included the customized manifest file. The next step is to create an Enterprise Application Archive (EAR) file. An EAR file is a standard JAR file, with an ear
extension, that contains other JAR files embedded within it. The JAR files can be EJB modules or any Java classes, such as an application client deployed in a JAR file. An EAR file can also contain a web module, identified by a war
extension. We shall see an example of this later in the book.
In our example the EAR file, which we name TimeService.ear
, will contain the EJB module TimeService.jar
and the client JAR file, Client.jar
. The Ant script to create an EAR file is:
<target name="package-ear" depends="package-ejb"> <jar destfile="${build.dir}/TimeService.ear" basedir="${build.dir}" includes="TimeService.jar Client.jar"/> </target>
Again we can use the jar -tvf
command to examine the contents of the EAR file:
C:\EJB3Chapter02\glassfish\lab2\build>jar -tvf TimeService.ear
...META-INF/
...META-INF/MANIFEST.MF
...Client.jar
.. TimeService.jar
We deploy the EAR file rather than the EJB module. The Ant script shows this:
<target name="deploy">
<exec executable="${glassfish.home}/bin/asadmin"
failonerror="true"
vmlauncher="false">
<arg line="deploy --user admin passwordfile
adminpassword ${build.dir}/TimeService.ear"/>
</exec>
</target>
Alternatively we can deploy an EAR file from the GlassFish administrator console. There is an option for deploying an enterprise application contained in an EAR file.
Finally we need to modify the Ant run-client script:
<target name="run-client"> <exec executable="${glassfish.home}/bin/appclient" failonerror="true" vmlauncher="false"> <arg line="-client ${glassfish.home}/domains/domain1/generated/xml/ j2ee-apps/TimeService/TimeServiceClient.jar -mainclass ejb30.client.Client"/>
Note that we start up the GlassFish ACC by executing the appclient
program. At deployment GlassFish creates a directory under the domains/domain1/generated/xml/j2ee-apps
directory. The name of this generated directory is the same as the EAR file without the extension, TimeService
in our case. In this directory GlassFish creates a JAR file with the same name as the directory but with Client
appended, TimeServiceClient.jar
in our case. The contents of this JAR file are those of the deployed EAR file together with some GlassFish generated deployment XML files. When we execute appclient
we need to supply the full pathname of TimeServiceClient.jar
as well as the fully qualified main application client class.
From this point on, throughout the book we assume that clients will always run in an ACC.
Stateless Session Bean's LifeCycle
It is import to stress that a session bean's lifecycle is controlled by the container and not the application. The following state diagram shows the stateless session bean's lifecycle:

The initial state of a stateless session bean is the does-not-exist state. This would be the case before a container starts up, for example. The next state is the method-ready pool. When the container starts up, it typically creates a number of stateless session bean instances in the method-ready pool. However the container can decide at any time to create such instances. In order to create an instance in the method-ready pool, the container performs the following steps:
- The bean is instantiated.
- The container injects the bean's
SessionContext
, if applicable. In our example we have not made use of theSessionContext
. TheSesionContext
is used by a bean to query the container about the bean's status or context. - The container performs any other dependency injection that is specified in the bean's metadata. Again in our example we have not specified any such metadata.
- The container then invokes a
PostConstruct
callback method if one is present in the bean. Again we have not invoked such a method in our bean example. ThePostConstruct
method would be used for initializing any resources used by the bean. For example, the session bean may make use of a JMS queue for sending messages. The connection queue used by JMS could be initialized in thePostConstruct
method. APostConstruct
method is called only once in the life of an instance, when it has transitioned from the does-not-exist state to the method-ready pool.
The container then calls a business method on the bean. Each business method call could originate from a different client. Conversely when a client invokes a business method on a stateless session bean any instance in the method-ready pool can be chosen by the container to execute the method.
After a business method has been invoked, the container may decide to destroy the bean instance (typically if the container decides there are too many instances in the method-ready pool) or reuse the instance on behalf of any client that invokes its business methods.
When the container does decide to destroy the instance it first invokes the PreDestroy
callback method if one is present in the bean. In our example we have not invoked such a method. The PreDestroy
method would be used for tidying up activities, such as closing connections that may have been opened in the PostConstruct
method. A PreDestroy
method is called only once in the life of an instance, when it is about to transition to the does-not-exist state.
These features of instance pooling and instance sharing mean that stateless session beans scale well to large number of clients.
The listing below shows a modified implementation of TimeServiceBean
with a SessionContext
and callback methods added. At this stage the methods merely print out messages. Later in this book we shall see more meaningful examples.
@Stateless public class TimeServiceBean implements TimeService { @Resource private SessionContext ctx; @PostConstruct public void init() { System.out.println( "Post Constructor Method init() Invoked"); } public String getTime() { System.out.println(ctx.getInvokedBusinessInterface()); Formatter fmt = new Formatter(); Calendar cal = Calendar.getInstance(); fmt.format("%tr", cal); return fmt.toString(); } @PreDestroy public void tidyUp() { System.out.println( "Pre Destruction Method tidyUp() Invoked"); } }
Note that there can be at most only one PostConstruct
and one PreDestroy
method. The methods can take any name but must be void
with no arguments. In the case of GlassFish, the messages printed out by these methods will appear in the container's log file, which can be viewed from the GlassFish administrator console.
Note the statement:
@Resource private SessionContext ctx;
This uses the @Resource
annotation to inject a reference to the javax.ejb.SessionContext
object into the ctx
field. Any kind of dependency injection, apart from two exceptions, is signaled with the @Resource
annotation. The two exceptions are @EJB
, which is used for injecting EJB references and @PersistenceContext
, which is used for injecting EntityManager
instances. We will cover the @PersistenceContext
annotation in Chapter 3.
In the getTime()
method we have added the statement:
System.out.println(ctx.getInvokedBusinessInterface());
This uses one of SessionContext
methods, namely getInvokedBusinessInterface()
. This method returns the session bean interface through which the current method is invoked. In our example the result is ejb30.session.TimeService
. This is a rather artificial use of getInvokedBusinessInterface()
since our session bean implements just one interface, namely TimeService
. However a session bean could implement two interfaces: a remote interface and a local interface. We will discuss local interfaces later in this chapter. One client could invoke TimeServiceBean
through the remote interface and another through the local interface, getInvokedBusinessInterface()
, which would inform us whether the current client is remote or local.
There are a more methods in the SessionContext
interface regarding the session beans current transaction and security contexts. We shall return to this in the chapters on Transactions and Security.
- Learning SQL Server 2008 Reporting Services
- After Effects CC影視后期制作實戰從入門到精通
- Pro/E Wildfire 5.0中文版入門、精通與實戰
- AutoCAD 2014中文版完全自學手冊
- 數字孿生體:第四次工業革命的通用目的技術
- 中文版Photoshop CS6平面設計從新手到高手(超值版)
- 中文版Rhino 5.0完全自學教程(第3版)
- Creo 4.0從入門到精通
- Moldflow 2021模流分析從入門到精通(升級版)
- 人人都能玩賺AI繪畫
- Photoshop CC2017圖像處理實例教程
- Origin科技繪圖與數據分析
- Excel 2010 Financials Cookbook
- Deep Inside osCommerce: The Cookbook
- Photoshop-CorelDRAW 基礎培訓教程