第 2 章 EJB 知识与运行环境设置( 1 )
1. 会话 Bean 分为两种:无状态( Stateless )和有状态( Stateful )。
2. 会话 Bean 的实现需要:
1) 远程接口( Remote Interface )
2) 本及接口( Local Interface )
3) 实现类( Bean Class )
3. 无状态会话 Bean
1) 实例池( Instance Pooling )技术
当无状态会话 Bean 被部署在应用服务器上的时候, EJB 容器会预先创建好该 Bean 的一些实例并放入对象池中。就像使用数据库连接池那样,当收到对 EJB 方法的访问请求时, EJB 容器会提取出一个实例为之服务,服务完毕后,再放回对象池中。
2) 由于无状态会话 Bean 更具性能优势,因此条件允许的情况下,应优先考虑使用无状态会话 Bean 。
4. 开发实现了远程接口的无状态 Bean
1) 首先,定义一个普通的接口,该接口应该包含业务逻辑方法。 EJB 客户端会使用这个接口类型的引用,去引用从 EJB 容器返回的存根( Stub )。
2) 其次,编写实现类
l 用到两个注释 @Stateless 和 @Remote
l @Stateless 的属性
name() :定义 EJB 的名称。在每个 EJB JAR 中,该名称必须是唯一的;但在 EAR 中,是可以重复的,因为 EAR 可以包含多个 EJB JAR ,而每个 JAR 可以有一个同名称的 EJB 。
mappedName() :指定 Bean 的全局 JNDI 名称,这个属性在 WebLogic 、 Sun Application Server 和 Glassfish 上有效。
l @Remote
为无状态会话 Bean 指定远程接口,接受 .class 类型的属性;
每个 Bean 可以有多个远程接口,每个接口可以用逗号分开,如 :
@Remote({Hello.class, HelloWorld.class, World.class})
5. 打包导出 JAR 文件
1) Eclipse 打包向导
2) Ant 打包任务
通过 build.xml 配置打包任务:
<?xml version="1.0" encoding="UTF-8"?>
<project name= "HelloWorld" basedir= "." >
<property environment= "env" />
<property name= "src.dir" value= "${basedir}/src" />
<property name= "jboss.home" value= "${env.JBOSS_HOME}" />
<property name= "jboss.server.config" value= "default" ></property>
<property name= "build.dir" value= "${basedir}/build" />
<property name= "build.classes.dir" value= "${build.dir}/classes" />
<path id= "build.classpath" >
<fileset dir= "${jboss.home}/client" >
<include name= "*.jar" />
</fileset>
<pathelement location= "${build.classes.dir}" />
</path>
<target name= "prepare" >
<delete dir= "${build.dir}" >
</delete>
<mkdir dir= "${build.dir}" />
</target>
<target name= "compile" depends= "prepare" description= " 编译 " >
<javac srcdir= "${src.dir}" destdir= "${build.dir}" >
<classpath refid= "build.classpath" >
</classpath>
</javac>
</target>
<target name= "ejbjar" depends= "compile" description= " 打包 " >
<jar jarfile= "${basedir}/${ant.project.name}.jar" >
<fileset dir= "${build.dir}" >
<include name= "**/*.class" />
</fileset>
</jar>
</target>
<target name= "depoly" depends= "ejbjar" description= " 部署 " >
<copy file= "${basedir}/${ant.project.name}.jar" todir= "${jboss.home}/server/${jboss.server.config}/deploy" >
</copy>
</target>
<target name= "undeploy" description= " 卸载 " >
<delete file= "${jboss.home}/server/${jboss.server.config}/deploy/${ant.project.name}.jar" ></delete>
</target>
</project>
3) 可以使用 http://localhost:8080/jmx-console 选择 service=JNDIView 来查看是否部署成功。
6. EJB 命名,如果没有为 EJB 指定全局名称, JBoss 会在部署的时候根据默认规则为其命名:
1) 打包在 EAR 中:
本地接口: EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local
远程接口: EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remote
2) 打包在 JAR 中:
本地接口: EJB-CLASS-NAME/local
远程接口: EJB-CLASS-NAME/remote
应当注意: EJB-CLASS-NAME 不包括包名。
7. JNDI 调用
1) 配置 JNDI 信息
l JNDI 驱动类名:
java.naming.factory.initial 或 Context.INITIAL_CONTEXT_FACTORY ,用于指定 InitialContext 工厂,类似于 JDBC 的数据库驱动类。 JBoss 提供的驱动类是 org.jnp.interfaces.NamingContextFactory 。
l 命名服务提供者 URL :
java.naming.provider.url 或 Context.PROVIDER_URL ,用于指定提供命名服务的主机地址和端口号,类似于 JDBC 的数据库 URL 。 JBoss 提供的 URL 格式为 jnp://host:port 。除主机名以外,其他部分都可以不写。
l java.naming.security.principal 和 java.naming.security.credential 用于指定用户标识(如用户名)和凭证(如密码)。当 EJB 提供安全服务时,必须提供这两个属性。即类似于 JDBC 连接数据库时的用到的用户名和密码。
2) 创建 InitialContext
如果客户端也运行在应用服务器内,则不需要为 InitialContext 设置上下文信息。应为应用服务器会把 JNDI 驱动类等上下文信息添加进系统属性,当使用 InitialContext 的无参数构造方法创建 InitialContext 时, InitialContext 内部会调用 System.getProperty() 方法从系统属性中获取必要的上下文信息。
3) 查找 EJB
使用 InitialContext 的 lookup 方法,根据 EJB 的 JNDI 名称,可以从 EJB 容器查找并返回回来一个存根对象。该存根对象实现了 EJB 的远程接口,它会将客户端的方法调用路由到应用服务器,应用服务器再将调用请求路由到真正的 EJB 实例上。
8. 开发实现了本地接口的无状态 Bean
1) 基本流程与远程接口相似
2) @Local 与 @Remote 用法也基本相同
3) 当 @Remote 和 @Local 都不存在时,容器会将 Bean Class 实现的接口,默认认为本地接口。
9. 开发实现了远程和本地接口的无状态 Bean
1) 实际应用中建议将 Bean 类同时实现 Remote 和 Local 接口。
2) 值得注意的是,实现了 Remote 和 Local 接口的 Bean Class 可以为远程访问和本地访问提供服务。由于无状态会话 Bean 不会跟踪维护会话的状态,因此即使会话已经失效了, Bean 实例可能仍然存活于对象池中。而 Bean 实例自身的属性变量并未变化。所以,对接下来的调用,不论远程访问还是本地访问,都存在交叉影响的可能,需要做好处理。
10. 实例池( Instance Pooling )
1) 主要用于无状态会话 Bean 和消息驱动 Bean ,而不能用于有状态会话 Bean 。
2) 实现很类似于 JDBC 连接池。
11. 无状态会话 Bean 的生命周期
1) 两种状态: does not exist 和 method-ready pool
2) 进入 method-ready pool 状态,会完成三步操作:
l 容器会调用无状态会话 Bean 实现类的 Class.newInstance() 方法,构造一个 Bean 实例。因此必须提供无参的 public 构造方法。
l 根据注释或 .xml 部署描述文件,将在 Bean 元数据中声明的资源注入进来。
l EJB 产生一个 post-construction 事件,该事件注册了一个回调方法,用来调用 Bean 类中标注了 @javax.annotation.PostConstruct 注释的方法。这种回调方法可以使用任何名称,但有如下限制: void 、无参数、不抛检查异常;每个 Bean 类只能有一个 @PostConstruct 回调方法,且实例的整个生命周期中只在此时调用一次。通常用来做初始化工作,比如打开资源等。
3) 处于 method-ready pool 状态
l 无状态会话 Bean 实例只在单个方法调用期间才与 EJB Object 关联。如果某个 Bean 实例正在处理请求,那么直到完成了这次处理,才可以被其他 EJB Object 关联使用。(作者没有仔细说明 EJB Object 是什么,以及和 Bean 实例的关系,但我猜可能就是后面所说到的骨架类( Skeleton )。在没有明确答案之前,姑且先这么认为)。
l 客户端通过注入或者 JNDI 查找 EJB 并获得应用,但引用的返回并不会导致无状态会话 Bean 实例的创建或者从池中被取出。直到 Bean 的方法被调用时,这些操作才会发生。
4) 离开 method-ready pool 状态
l Bean 实例被删除,即回到 does not exist 状态。
l EJB 产生一个 pre-destroy 事件,该事件注册了一个回调方法,用来调用 Bean 类中标注了 @javax.annotation.PreDestroy 注释的方法。该类型的回调方法与 @PostConstruct 类似,相似的限制。每个 Bean 类只能指定一个 @PreDestroy 方法,也只在生命周期的此时被调用一次。通常用来做清理工作,比如关闭打开的资源等。
12. 有状态会话 Bean ( Stateful Session Bean )
1) 每一次 lookup() 调用都会创建一个新的 Bean 实例与之对应。
2) 如果想一直使用某一 Bean 实例,可以在客户端使用 Session 或者其他方式来缓存这个存根。
3) 用状态会话 Bean 必须实现 Serializable 接口。
13. 激活机制( Activation Mechanism )
l 钝化( Passivation )
当某一 Bean 实例很少被调用或者 EJB 容器需要减小系统开销的时候,容器会从内存中回收 Bean 实例,并连同其保存的会话状态,一起序列化到硬盘中,释放其占用的内存。
l 激活( Activation )
直到客户端再次发出请求, EJB 容器才会根据所关联的 EJB 对象重新恢复 Bean 实例的状态。容器会重新创建一个新的 Bean 实例,并将钝化期间保存的内容逐一设置数据成员。 EJB 对象像钝化之前一样,将方法调用委托给这个 Bean 实例。
14. 有状态会话 Bean 的生命周期
1) 三种状态: does not exist 、 method-ready 、 passivated
2) 进入 method-ready 状态,只有当客户端第一次调用有状态会话 Bean 方法的时候,才会发生。完成三个步骤:
l 容器会调用有状态会话 Bean 的 Class.newInstance() 方法,创建 Bean 实例。因此需要提供无参数的 public 构造方法。
l 完成引来注入。
l 调用 Bean 类中定义的 @PostConstruct 回调方法。
3) 处于 method-ready 状态
在此状态下, Bean 实例可以保持 Session 状态以及打开着的外部资源,为请求服务。
4) 离开 method-ready 状态
l 离开 method-ready 状态, Bean 实例不是进入 does not exist 状态,就是进入 passivated 状态,即不是被删除就是被钝化。
l 如果客户端调用了具有 @Remove 注释的 Bean 方法,则调用完成后, Bean 实例会被立即清除,进入 does not exist 状态。
l 如果 Bean 实例超时了,则容器会将 Bean 实例从 method-ready 转移到 does not exist 状态;此时,容器是否会调用 @PreDestroy 回调方法,完全取决于容器的具体实现。
l 如果 Bean 实例所处的事务还处在处理期间, Bean 是不能超时的。
5) passivated 状态
l 当进入 passivated 状态时, @PrePassivate 回调方法会被调用。
l 如果在此状态下超时,容器也会将 Bean 实例直接从 passivated 状态转移为 does not exist 状态,同时 @PreDestroy 回调方法也不会被调用。
6) 系统异常
l 无论何时,只要 Bean 方法抛出系统异常,容器就会将相关 EJB Object 置为无效,并销毁 Bean 实例。 Bean 实例会直接变为 does not exist , @PreDestroy 也不会被调用。
l 系统异常包括:运行时异常及其子类、 java.rmi.RemoteException 及其子类在内的,任何未标注 @ApplicationException 注释的非检查异常。 EJB Exception 是一个运行时异常,因此也会被当作系统异常。
15. EJB 的调用机制
1) 当 EJB 部署到应用服务器上的时候, EJB 容器为 EJB 生成存根类( Stub )和骨架类( Skeleton )。存根类和骨架类对开发者来说,是不可见的。
2) 客户端使用 lookup() 方法在应用服务器的 JNDI 树中查找 EJB ,应用服务器将存根对象序列化后返回给客户端。此时客户端远程接口引用的是存根对象,接着开始业务方法的调用。
3) 业务方法调用过程:
l 客户端调用远程接口的业务方法,而实际上就是调用存根对象的业务方法(存根实现了远程接口);
l 方法调用经过 IIOP Runtime 被转换成 CORBA IIOP 消息发往应用服务器;
l 应用服务器接收到消息后,交由骨架类处理,骨架类解析 IIOP 协议消息,并调用 Bean 实例的业务方法;
l 骨架类将 Bean 实例业务方法的返回值,转换为 CORBA IIOP 消息发回客户端;
l 存根类解析收到的 CORBA IIOP 应答消息,将返回值传给客户端。