官术网_书友最值得收藏!

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.

The Program Directory Structure

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 the C:/glassfish directory, so the contents of env.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, is adminadmin. So the contents of adminpassword are:
    AS_ADMIN_PASSWORD=adminadmin
    
    
  • src: This subdirectory contains the source code. Recall the client program, Client.java, is placed in the ejb30.client package. The package name is reflected in the subdirectory structure. Subdirectories have also been created to correspond to the ejb30.session package in which TimeService and TimeServiceBean 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.

Building the Application

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:

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:

  1. The bean is instantiated.
  2. The container injects the bean's SessionContext, if applicable. In our example we have not made use of the SessionContext. The SesionContext is used by a bean to query the container about the bean's status or context.
  3. 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.
  4. 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. The PostConstruct 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 the PostConstruct method. A PostConstruct 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.

主站蜘蛛池模板: 平谷区| 久治县| 东莞市| 新沂市| 肃宁县| 宝应县| 大安市| 安达市| 博客| 辉南县| 威海市| 惠来县| 岳阳市| 云龙县| 时尚| 杭锦后旗| 锦屏县| 福海县| 务川| 兰西县| 高州市| 绥阳县| 会泽县| 大邑县| 缙云县| 新建县| 辉县市| 新昌县| 延寿县| 鸡西市| 紫云| 定安县| 湘潭县| 林口县| 佛冈县| 卢氏县| 岢岚县| 阿拉善盟| 扶绥县| 青冈县| 德格县|