会话 Bean(Session Bean)

来源:互联网 发布:单反摄影书籍 知乎 编辑:程序博客网 时间:2024/04/29 00:01
Session Bean 是实现业务逻辑的地方。简单地说,像我们要实现两数相加或是从数据库中读取数据,都是通过Session Bean 来实现。根据是否可以维护会话状态,Session Bean 分为有状态 bean 和无状态 bean。有状态 bean 可以维护会话状态,无状态 bean 不维护会话状态。要维护会话状态,意味着 EJB 容器要为每个用户创建一个 bean实例,并通过该实例保存着与用户的会话状态。不维护会话状态,意味着一个 bean 实例不需要保存与某个用户的会话状态,这时一个 bean 实例可以为多个用户服务。 

要开发一个 Session Bean,  我们需要定义接口和 Bean class。其中接口分为远程(remote)和本地(local)接口。EJB3.0在中,不要求你同时实现 remote 和 local 接口,但实现两者是比较好的做法。 
 远程接口(remote interface):定义了 session bean 的业务方法,这些方法可以被来自 EJB 容器之外的应用访问到。 
 本地接口(local interface):同样定义了 session bean 的业务方法,这些方法可以被同处于 EJB 容器内的其它应用使用。因为 local 接口允许 bean 之间直接通过内存交互,没有分布式对象协议的开销,从而改善了性能。 
 Bean 类(bean class):bean class 包含了业务逻辑,它必须具备一个远程或本地接口。在 Bean 类,我们应该实现接口的业务方法,尽管这并不是必须的,但我们没理由不这样做。 

1 Stateless Session Beans(无状态 bean)开发 

由于无状态会话 Bean 不维护会话状态,意味着一个 bean 实例可以为多个用户服务。因此 EJB 容器使用实例池化技术管理无状态会话 Bean。简单的说就是:当无状态会话 Bean 部署到应用服务器时,EJB 容器会为它预先创建一些 bean 实例放在对象池。当有用户访问 EJB 方法时,EJB 容器会从对象池中取出一个实例为之服务,服务完了就回到对象池。当下一个用户再访问 EJB 方法时,EJB 容器有可能再次把该实例取出来为之服务。正因如此,无状态会话 Bean 只需要少量的实例就可以为成百上千的用户服务,大大提高了系统性能。 

由于无状态会话 Bean 能够支持多个用户,并且通常在 EJB 容器中共享,可以为需要大量客户的应用提供更好的扩充能力。无状态会话 Bean 比有状态会话 Bean 更具性能优势,在条件允许的情况下开发人员应该首先考虑使用无状态会话 Bean。 

1.1开发只实现 Remote 接口的无状态 Session Bean 
在开发前,先熟悉一下本例子的调用流程图: 
 
1. 浏览器请求 Test.jsp 文件 
2. 应用服务器的 JSP 引掣编绎 Test.jsp 
3. Test.jsp 通过 JNDI 查找获得 HelloWorld EJB 的存根对象,然后调用 SayHello()方法,EJB 容器截获到方法调用。 
4. EJB 容器调用 HelloWorld 实例的 SayHello()方法. 

现在我们就开始本例子的开发。首先在 Eclipse 中新建一个普通的 java 项目,然后把[Jboss 安装目录]/client 下的所有 jar 文件加入到项目的构建路径中。如果不需要在项目中使用单元测试用例或普通 J2SE 调用 EJB,你只需要加入 javaee.jar,该文件在本书源代码 lib/javaee 目录下。接下来开始代码编写。 

开发步骤如下: 
第一步:定义一个包含业务方法的接口。这个接口不需要包含任何注释,它是一个普通的 java 接口。调用 EJB的客户端使用这个接口引用从 EJB 容器返回的存根(stub)。代码如下:HelloWorld.java 
Java代码  收藏代码
  1. package com.foshanshop.ejb3;  
  2. public interface HelloWorld {  
  3.     public String SayHello(String name);  
  4. }  

第二步:编写 Bean class。 
HelloWorldBean.java 。Bean 类推荐的命名方式是:接口+Bean ,如: HelloWorldBean 。 
Java代码  收藏代码
  1. package com.foshanshop.ejb3.impl;  
  2. import com.foshanshop.ejb3.HelloWorld;  
  3. import javax.ejb.Remote;  
  4. import javax.ejb.Stateless;  
  5. @Stateless  
  6. @Remote ({HelloWorld.class})  
  7. public class HelloWorldBean implements HelloWorld {  
  8.   
  9.   
  10.     public String SayHello(String name) {  
  11.         return name +"说:你好!世界,这是我的第一个EJB3哦.";  
  12.     }  
  13. }  

在 Bean 类上面有两个注释@Stateless 和@Remote,@Stateless 注释指明这是一个无状态会话 Bean。@Stateless 注释的定义如下: 
Java代码  收藏代码
  1. Package javax.ejb;  
  2. @Target(TYPE) @Retention(RUNTIME)  
  3. public @interface Stateless {  
  4.    String name( ) default "";  
  5.    String mappedName() default "";  
  6. }  

name()属性用于指定 session bean 的 EJB 名称。该名称在 EJB Jar 包中必须是全局唯一的,而在 EAR 中却可以重复(因为 EAR 可以包含多个 EJB Jar,而每个 jar 可以存在一个同名的 EJB,在 EAR 中要定位某个 EJB,可以这样使用:xxx.jar#HelloWorldBean)。如果不指定该属性,默认就是 bean class 的非限定名称。对本例而言,EJB 名称默认为 HelloWorldBean。 
mappedName()属性指定 Bean 的全局 JNDI 名称,这个属性在 weblogic,Sun 应用服务器和 glassfish 起作用。 

@Remote 注释指定这个无状态 Bean 的 remote 接口。Bean 类可以具有多个 remote 接口,每个接口之间用逗号分隔,如:@Remote ({HelloWorld.class,Hello.class,World.class})。如果你只有一个接口,你可以省略大括号,对于本例而言,可以写成这样:@Remote (HelloWorld.class)。 

经过上面两步,一个HelloWorld EJB就开发完了。现在我们把它发布到Jboss中。在发布前我们需要把它打成Jar包。JAR包的方法有很多,打如使用 jar 命令、集成开发工具或者Ant。下面为你介绍两种常用的打包方式:Eclipse打包向导和 Ant 打包。 

Eclipse 打包向导 
在 Eclipse 开发环境下,可以通过导出向导进行打包。在项目名称上点击右键,在跳出的菜单中选择“Export(导出),在“Export”对话框选择“JAR file”点“Next”,在“select the resources to export(选择要导出的资源)”一栏,展开你的项目并选择需要打包的文件。然后选择一个存放目录及文件名。点“Finish”结束打包。 

Ant 打包任务 
使用 Ant 打包是比较方便的,也是作者推荐的打包方式。我们可以在项目根目录下建立一个名为 build.xml 的 xml文件,然后在 xml 文件里面定义我们的打包任务。如下: 
Xml代码  收藏代码
  1. <?xml version="1.0"?>  
  2. <project name="HelloWorld" default="ejbjar" basedir=".">  
  3.    <property environment="env" />  
  4.    <property name="src.dir" value="${basedir}/src" />  
  5.    <property name="jboss.home" value="${env.JBOSS_HOME}" />  
  6.    <property name="build.dir" value="${basedir}/build" />  
  7.    <property name="build.classes.dir" value="${build.dir}/classes" />  
  8.   
  9.   
  10.    <!-- Build classpath -->  
  11.    <path id="build.classpath">  
  12.        <fileset dir="${jboss.home}/client">  
  13.               <include name="*.jar" />  
  14.         </fileset>  
  15.         <pathelement location="${build.classes.dir}" />  
  16.     </path>  
  17.     <target name="prepare" depends="clean">  
  18.         <mkdir dir="${build.dir}" />  
  19.         <mkdir dir="${build.classes.dir}" />  
  20.     </target>  
  21.     <target name="compile" depends="prepare" description="编绎">  
  22.         <javac srcdir="${src.dir}" destdir="${build.classes.dir}" debug="on"  
  23. deprecation="on" optimize="off" includes="**">  
  24.               <classpath refid="build.classpath" />  
  25.         </javac>  
  26.     </target>  
  27.     <target name="ejbjar" depends="compile" description="创建EJB发布包">  
  28.         <jar jarfile="${basedir}/HelloWorld.jar">  
  29.               <fileset dir="${build.classes.dir}">  
  30.                  <include name="**/*.class" />  
  31.               </fileset>  
  32.               <metainf dir="${src.dir}/META-INF">  
  33.                  <include name="*.xml" />  
  34.               </metainf>  
  35.         </jar>  
  36.     </target>  
  37.     <target name="clean">  
  38.         <delete dir="${build.dir}" />  
  39.     </target>  
  40. </project>  

上面建立了一个名为 HelloWorld 的 Ant 项目,default="ejbjar"指定运行 Ant 时,如果没有给定任务名称,则默认执行 ejbjar 任务。basedir="."指定项目的路径为 build.xml 文件所在目录。 
<property environment="env" />用于引用操作系统的环境变量。 
<property name="jboss.home" value="${env.JBOSS_HOME}" />定义了一个名为 jboss.home 的属性,它的值引用名为 JBOSS_HOME 的环境变量(环境变量 JBOSS_HOME 是在安装 jboss 时让大家设置的)。 
<property name="build.dir" value="${basedir}/build" />定义一个名为 build.dir 的属性,它的值指向项目路径下 build目录,该目录用于存放编绎后的临时文件。 
<property name="build.classes.dir" value="${build.dir}/classes" />定义一个名为 build.dir 的属性,它的值指向项目路径下 build/ classes 目录,该目录用于存放编绎后的 class 文件。 
<path id="build.classpath">节点定义了一个 id 为 build.classpath 的类路径,类路径包含[jboss 安装目录]\client 下的所有 jar 文件及/build/ classes 下的所有类文件。 
<target name="prepare" depends="clean">节点用于在项目路径下创建 build 和/build/ classes 文件夹。该任务依赖clean 任务,执行 prepare 任务前会先执行 clean 任务。 
<target name="compile" depends="prepare" description="编绎">节点定义了一个编绎任务,该任务调用 javac 对 src目录下的源文件进行编绎。classpath 引用 id 为 build.classpath 的类路径。编绎后的 class 文件存放在/build/classes目录,在任务执行前会先执行 prepare 任务。 
<target name="ejbjar" depends="compile" description="创建 EJB 发布包">节点定义了一个打包任务,该任务调用 jar命令对/build/classes 目录下的所有 class 文件进行打包,并且把 src/META-INF 目录下的所有 xml 文件打进 jar 文件的 META-INF 目录。生成后的jar文件存放在项目的根目录下,名为 HelloWorld.jar。在任务执行前会先执行 compile任务。 

当 build.xml 编写完成后,我们可以在 eclipse 中执行 Ant 任务。方法是:打开 build.xml 文件,在窗口右边的 Outline(大纲)中右键点击任务名称,在出现的菜单中点击“Run As(运行方式)”-“Ant Build(Ant 构建)” 

如果你的 eclipse 中没有大纲窗口,你可以点击“window”-“show view(显示视图)-“Outline” 

不管使用何种打包方式,一个 EJB 打包后应具有以下目录结构: 
  EJB 应用根目录 
  | -- **/*.class (你的.class 文件) 
  | -- META-INF 
          | -- MANIFEST.MF (如果使用工具打包,该文件由工具自动生成) 

当 HelloWorld 打成 jar 文件后,我们把它发布到 Jboss。发布前先检查 jboss 是否已经启动,如果没有启动,我们可以进入[jboss 安装目录]/bin,  双击 run.bat 启动 Jboss。不指定启动参数的情况下,Jboss 默认使用 default 配置项。 
把 jar 文件拷贝到[jboss 安装目录]\server\default\deploy\目录。观察 Jboss 控制台输出,如果没有抛出例外并看到下面的输出界面,发布就算成功了。 

当 EJB 发布成功后,Jboss 容器会为它生成一个全局 JNDI 名称,我们可以利用这一点进一步判断 EJB 发布是否成功。我们进入 Jboss 的管理台查看它的 JNDI 名称,输入下面 URLhttp://localhost:8080/jmx-console/点击 service=JNDIView,查看 EJB 的 JNDI 名称。 

在出现的页面中,找到“List of MBean operations:”栏。 
点击“Invoke”按钮 

在出现的页面中,我们可以看到 JBOSS 的 JNDI 树,它的命名约定如下: 
(1)java:comp (java:comp namespace) 
  这个上下文环境和其子上下文环境仅能被应用组件内部访问和使用 
(2)java:(java: Namespace) 
  子上下文环境和绑定的对象只能被处在同一个 JVM 内的客户访问 
(3)Global JNDI Namespace 
  上下文环境能被所有客户访问,不管它们是否处在同一个 JVM 内。 

当 EJB 发布到 Jboss 时,如果我们没有为它指定全局JNDI名称或修改过其默认EJB名称,Jboss就会按照默认的命名规则为 EJB 生成全局 JNDI 名称,默认的命名规则如下: 
如果把 EJB 作为模块打包进后缀为*.ear 的 JAVA EE 企业应用文件,默认的全局 JNDI 名称是本地接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local远程接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remoteEAR-FILE-BASE-NAME 为 ear 文件的名称,EJB-CLASS-NAME 为 EJB 的非限定类名。 
例: HelloWorld 应用作为 EJB 模块打包进名为 HelloWorld.ear 的企业应用文件,把它的远程接口的 JNDI 名称是: 
HelloWorld/HelloWorldBean/remote 
如果把 EJB 应用打包成后缀为*.jar 的模块文件,默认的全局 JNDI 名称是 
本地接口:EJB-CLASS-NAME/local 
远程接口:EJB-CLASS-NAME/remote 
例:把 HelloWorld 应用打包成 HelloWorld.jar 文件,它的远程接口的 JNDI 名称是:HelloWorldBean/remote注意:EJB-CLASS-NAME 是不带包名的,如 com.foshanshop.ejb3.impl.HelloWorldBean 只需取 HelloWorldBean。如果你通过@Stateless.name()、     @Stateful.name()及其等价的 XML 指定了 EJB 名称,那么上面的 EJB-CLASS-NAME应该换为 EJB 名称,此时的 JNDI 名称格式如:EJB 名称/remote、EAR 文件名/EJB 名称/remote。 

在 Global JNDI Namespace 一栏,我们看到了 HelloWorldBean 的远程接口的 JNDI 名称为 HelloWorldBean/remote。意味着 EJB 已经发布成功。接下来我们看看客户端如何访问它。 
Test.jsp 
Jsp代码  收藏代码
  1. <%@ page contentType="text/html; charset=GBK"%>  
  2.  <%@ page import="com.foshanshop.ejb3.HelloWorld, javax.naming.*,  
  3.  java.util.Properties"%>  
  4.  <%  
  5.         Properties props = new Properties();  
  6.         props.setProperty("java.naming.factory.initial",  
  7.  "org.jnp.interfaces.NamingContextFactory");  
  8.         props.setProperty("java.naming.provider.url""localhost:1099");  
  9.         try {  
  10.             InitialContext ctx = new InitialContext(props);  
  11.             HelloWorld helloworld = (HelloWorld)  
  12.  ctx.lookup("HelloWorldBean/remote");  
  13.             out.println(helloworld.SayHello("佛山人"));  
  14.         } catch (NamingException e) {  
  15.             out.println(e.getMessage());  
  16.         }  
  17.  %>  
0 0