EJB的简单介绍和使用

17.1 为什么需要EJB

要想知道为什么要使用EJB,就需要知道"面向服务"的概念。"面向服务",是软件开发过程中,异构环境下模块调用的一个比较重要的思想。同样,面向服务也只是一种设计思想,不是一种编程技术。由"面向服务"的思想,业界提出了"面向服务的体系结构(Service Oriented Architecture, SOA)"的概念。

用一个实际案例来引入"面向服务"的概念。在某些大型应用场合,我们要在不同的运行环境之间传递数据,比如:

A公司需要从B公司的数据库中查询一些内容之后返回,进行处理,如何实现?

最简单的结构,如图17-1所示:

EJB的简单介绍和使用_第1张图片 
图17-1 最简单的两公司之间互相调用的结构
但是,以上程序在实际操作中,是不能实现的。因为JDBC代码写在A公司部分,那就必须让A公司的程序知道B公司数据库的详细结构。在一般情况下,这是不合理的。比如,一个公司通过自己的平台向银行转账,不可能知道银行数据库的结构。于是,程序可以变为如图17-2所示结构:
EJB的简单介绍和使用_第2张图片 
(点击查看大图)图17-2 改进的结构

该结构详述如下:B公司编写自己的程序,访问数据库,对外发布一个接口,并发布一个服务的名称。我们知道,接口里面并没有核心代码。该接口也被A公司获取,A公司网上寻找相应的B公司发布的服务名称,然后通过接口调用B公司程序里面的方法。

但是,该技术不是简单就可以实现的,因为A公司和B公司的程序,可能运行在不同的虚拟机内,甚至可能是不同的语言。EJB可以解决A公司和B公司使用的都是Java语言,但是处于不同的Java虚拟机的情况。

该问题的原型是:一个Java虚拟机内的对象能否远程调用另外一个Java虚拟机里面的对象内的方法?实际上,在Java内,该技术可以用RMI(远程方法调用)实现。而EJB的底层,就是用RMI实现的。

实际上,即使是在同一个Java虚拟机内,将某个功能以服务的形式对外发布,被该虚拟机中的另一个模块调用,也是可以大大降低耦合性的。因为模块之间打交道的,只是一个接口和一个服务名称。

不过,顺便需要提到的是,如果两个程序使用的是不同语言平台,如一个是C,一个是Java,业界中也提出了一些方法来解决数据交换问题,如WebService、CORBA等。读者可以参考相关文献。

17.2 EJB框架的基本原理

17.2.1 EJB框架简介

如前所述,EJB实际上是服务器端运行的一个对象,只不过该对象所对应的类并不被客户端所知,该对象对外发布的是一个服务名称,并提供一个可以被客户端调用的接口。通俗点说,EJB就是一个可以被客户端调用,但是并不让客户端知道源代码的类的对象。

因此,EJB并不是普通的Java Bean,普通的JavaBean是一个符合某种规范的Java类文件,只能作为一个类被调用,只有调用的时候才运行,是一个进程内组件。而EJB并不是一个单独的文件,其组成包括:

1. 类文件:实现基本方法的类,封装了需要实现的商务逻辑,数据逻辑或消息处理逻辑,具有一定的编程规范,代码不能被客户端得知。

2. 接口文件:接口是EJB组件模型的一部分,里面提供的方法一般和需要被远程调用的方法一致,一般情况下,要求类文件必须和接口中的定义保持一致性。

3. 必要的情况下,编写一些配置文件,用于描述EJB部署过程中的一些信息。

EJB可以作为一个服务被调用,可以单独运行,是一个进程级组件。EJB中还提供了一些安全管理、事务控制功能,使得我们调用EJB时,不需要太多地束缚于这些问题的编码。

EJB 定义了四种类型的组件:

1. Session Bean:会话Bean,封装业务逻辑,负责完成某个操作。根据生命周期的不同,又可以分为:

(1) Stateless Session Bean: 无状态会话Bean,不存储用户相关信息,一般说来,在服务器端,一个Bean对象可能为很多客户服务,如图17-3所示:

EJB的简单介绍和使用_第3张图片 
图17-3 无状态会话Bean的使用

由于一个Bean对象可能为多个客户服务,因此,一般不在对象内保存某个客户的状态,保存也没有意义。

(2) Stateful Session Bean: 有状态会话Bean,可以存储用户相关信息,在服务器端,一个Bean对象只为一个客户服务,如图17-4所示:

EJB的简单介绍和使用_第4张图片 
图17-4 有状态会话Bean的使用

由于一个Bean对象只为一个客户服务,因此,可以在对象内保存某个客户的状态。

2. Entity Bean:实体Bean,类似Hibernate,封装数据库中的数据,代表底层数据的持久化对象,把表中的列映射到对象的成员,主键在实体Bean中具有唯一性,一个实体Bean对象对应表中的一行,这将在下一章讲解。

3. Message Driven Bean:消息驱动Bean,是一种异步的无状态组件,和无状态会话组件具有相似性,是JMS消息的消费者,可以和JMS配合起来使用。

17.2.2 EJB运行原理

本章所讲解的EJB,特指会话Bean。

在EJB中,常用的的组件有:客户端、接口(远程接口或者本地接口)、EJB实现类、JNDI名称等。它们之间的关系如图17-5所示:

EJB的简单介绍和使用_第5张图片 
图17-5 EJB组件之间的关系

对于一个业务操作,其执行步骤为:

首先,服务器端将EJB发布为一个JNDI名称,并提供一个接口文件。不过,值得注意的是,如果客户端和EJB运行在同一个容器内,可以提供的是本地(Local)接口,如果运行在不同的Java虚拟机内,提供的是远程(Remote)接口。接下来步骤如下:

1. 客户端向服务器发起连接,在服务器上寻找相应的JNDI名称,如果找到,返回一个对象。

2. 客户端将该对象强制转换为接口类型。

3. 客户端调用接口中的方法,实际上调用了服务器端EJB内的方法。

因此,利用EJB编程,有以下几个步骤:

1. 编写EJB实现类。

2. 编写接口。

3. 部署到服务器中,设定JNDI名称。

4. 编写客户端,并将接口拷贝给客户端,将JNDI名称公布,客户端调用EJB。

17.3 EJB框架的基本使用方法

该部分内容使用实际案例进行讲解。以一个银行系统为例,银行系统中提供一个"根据美元计算人民币"的功能,我们知道,美元必须乘以相应的汇率才能得到人民币,而汇率可能保存在银行的数据库中,该数据库结构不能对外公开。因此,客户端必须在不知道数据库结构的情况下,调用银行系统中"根据美元计算人民币"的方法,这就可以使用EJB实现。

本例中,需要建立远程接口和实现类。因为"根据美元计算人民币"的方法,可能是被远程调用的。

17.3.1 建立EJB项目

接下来就开始编写这个项目,打开MyEclipse,新建一个EJB项目,如图17-6所示:

 
图17-6 新建EJB项目
弹出"New EJB Project"对话框,确定项目名称。注意,"J2EE Specification Level"中一定要选定"Java EE5.0 - EJB3",否则无法支持EJB3。把界面下方的其他勾选去掉。如图17-7所示:
EJB的简单介绍和使用_第6张图片 
图17-7 新建EJB项目

如前所述,我们需要建立Bean的实现类和Bean的接口,由于接口最终需要被客户端使用,因此,适合单独放在一个包内。此处,可以在该项目中建立接口所在包:itf;以及实现类所在的包:impl。注意,此处的命名可能不一定规范,但是主要是为了便于理解,说明问题。建立好的项目如图17-8所示:

EJB的简单介绍和使用_第7张图片 
图17-8 新建EJB项目结构

17.3.2 编写远程接口

远程接口提供了客户端和服务器端的通信桥梁,在里面只有一个函数,就是可能被远程调用的函数。代码如下:

Convert.java

   
   
   
   
  1. package itf;  
  2.  
  3. public interface Convert {  
  4.     public String getRmb(String usd);  

很显然,该代码非常简单。该代码被客户端使用,也很方便。

17.3.3 编写实现类

Bean的实现类运行在服务器端,包含了核心代码。在"由美元计算人民币"的方法中,本来需要查询服务器端的数据库,为了简单起见,我们给定一个汇率值,不影响知识的理解。代码如下:

ConvertBean.java

   
   
   
   
  1. package impl;  
  2. import itf.Convert;  
  3. public class ConvertBean implements Convert {  
  4.     public String getRmb(String usd){  
  5.         //从数据库查询汇率,此处简化,假如汇率是6.0  
  6.         double rate = 6.0;  
  7.         double dblUsd = Double.parseDouble(usd);  
  8.         double dblRmb = dblUsd * rate;  
  9.         String rmb = String.valueOf(dblRmb);  
  10.         return rmb;  
  11.     }  

该代码很简单,Bean的实现类,实现了相应的接口。

17.3.4 配置EJB

编写了EJB实现类,还无法确定该EJB是否能够被远程调用,并且无法确定该会话Bean是有状态的还是无状态的。因此,需要进行配置。

在较早版本的EJB中,需要进行比较复杂的配置,编写xml配置文件,在EJB3中,你可以选择编写配置文件,也可以将配置在代码中标明。方法是:修改ConvertBean的源代码:

ConvertBean.java

   
   
   
   
  1. package impl;  
  2. import itf.Convert;  
  3. import javax.ejb.Remote;  
  4. import javax.ejb.Stateless;  
  5.  
  6. @Stateless (mappedName="ConvertBean")  
  7. @Remote  
  8.  
  9. public class ConvertBean implements Convert {  
  10.     public String getRmb(String usd){  
  11.         //从数据库查询汇率,此处简化,假如汇率是6.0  
  12.         double rate = 6.0;  
  13.         double dblUsd = Double.parseDouble(usd);  
  14.         double dblRmb = dblUsd * rate;  
  15.         String rmb = String.valueOf(dblRmb);  
  16.         return rmb;  
  17.     }  
注意,在该代码类定义之前,定义了:

   
   
   
   
  1. @Stateless (mappedName="ConvertBean")  
  2. @Remote 

表示:

1. 确定该EJB是可以被远程调用的。

2. EJB的JNDI名称为"ConvertBean",客户端寻找该EJB时,所使用的名字为"ConvertBean#itf.Convert",实际上是相当于寻找里面的接口。注意,在不同厂商的服务器中,JNDI格式有所不同。

2. 该EJB是无状态的会话Bean。

编写完毕,项目结构如图17-9所示:

EJB的简单介绍和使用_第8张图片 
图17-9 EJB项目结构

17.3.5 部署EJB

接下来就是将EJB部署到服务器中去。默认情况下,Tomcat不支持EJB,支持EJB的服务器有WebLogic、WebSphere、JBoss等,此处我们使用WebLogic10,以及其内部配置的用户服务器域:base domain,并已经在MyEclipse中对其进行了配置绑定。具体安装过程,参考第1章的内容。

点击工具条上的"部署"按钮,如图17-10所示:

 
图17-10 部署按钮
打开部署窗口,如图17-11所示:
EJB的简单介绍和使用_第9张图片 
(点击查看大图)图17-11 部署窗口
选择项目名称,点击"Add"按钮,出现如图17-12所示的界面:
EJB的简单介绍和使用_第10张图片 
(点击查看大图)图17-12 部署窗口

在该窗口中,选择"WebLogic 10.x",在下方选择"以目录形式部署"或者"以压缩包形式部署",系统将会在最下面显示部署的路径。此处选择"以目录形式部署"。完成。

接下来运行WebLogic服务器。如果MyEclipse和WebLogic已经绑定(参考第1章),工具条上会出现WebLogic服务器的打开菜单,如图17-13所示:

EJB的简单介绍和使用_第11张图片 
(点击查看大图)图17-13 打开WebLogic
可以打开WebLogic。打开之后,在浏览器中输入: http://localhost:7001/console ,打开控制台,输入账号密码,登录,进入控制台,在"Domain Structure"中,点击"Deployments",如图17-14所示:
EJB的简单介绍和使用_第12张图片 
图17-14 Domain Structure
在界面右方显示如图17-15所示:
EJB的简单介绍和使用_第13张图片 
(点击查看大图)图17-15 显示EJB
点击该EJB链接最左边的"+"号,出现如图17-16所示的界面,显示了EJB详细信息:
EJB的简单介绍和使用_第14张图片 
图17-16 EJB详细信息

该详细信息中,在"EJBs"下,名称"ConvertBean",注意,这并不是JNDI名称,知识该EJB实现类的类名称。

17.3.6 远程调用该EJB

该EJB被部署之后,就可以被远程调用了。很明显,要想远程调用该EJB,必须满足:

1. 得知服务器是WebLogic,因为不同的服务器连接方式可能不一样。

2. 得知服务器的IP地址和端口。

3. 拥有该EJB的远程接口的class文件,得知服务器端EJB的JNDI名称,如前所述,名称为:"ConvertBean#itf.Convert"。

建立普通的项目Prj17_Test,将远程接口拷贝到该项目中去,并且建立一个TestConvert.java,项目结构如图17-17所示:

EJB的简单介绍和使用_第15张图片 
图17-17 项目结构

编程步骤如下:

1. 确定连接目标:

   
   
   
   
  1. ……  
  2. Hashtable table = new Hashtable();  
  3. table.put(Context.INITIAL_CONTEXT_FACTORY,  
  4. "weblogic.jndi.WLInitialContextFactory");  
  5. table.put(Context.PROVIDER_URL,"t3://localhost:7001");  
  6. …… 

注意,此处用到了weblogic.jndi.WLInitialContextFactory,是WebLogic中专门负责初始化上下文对象的类,因此,本项目中,需要导入WebLogic相关开发包。方法是:右击项目名称,选择"Properties",如图17-18所示:

EJB的简单介绍和使用_第16张图片 
图17-18 选择项目属性
在跳出的窗口中,找到"Java Build Path",切换到"Library",如图17-19所示:
EJB的简单介绍和使用_第17张图片 
(点击查看大图)图17-19 属性窗口

点击"Add External JARs",找到%WebLogic安装目录%/server/lib/weblogic.jar,导入。如图17-20所示:

 
(点击查看大图)图17-20 导入效果
2. 查询服务器中的JNDI名称:
   
   
   
   
  1. ……  
  2.         Context context = new InitialContext(table);  
  3.         Convert convert = ( Convert) context.lookup(jndiName);  
  4. …… 

3. 调用接口:
   
   
   
   
  1. ……  
  2.         String rmb = convert.getRmb(usd);  
  3.         System.out.println(rmb);  
  4. …… 

整个文件的代码为:

   
   
   
   
  1. TestConvert1.java  
  2. import itf.Convert;  
  3. import java.util.Hashtable;  
  4. import javax.naming.Context;  
  5. import javax.naming.InitialContext;  
  6. public class TestConvert1 {  
  7.     public static void main(String[] args) throws Exception{  
  8.         String usd = "1234";  
  9.         String jndiName = "ConvertBean#itf.Convert";  
  10.  
  11.         Hashtable table = new Hashtable();  
  12. table.put(Context.INITIAL_CONTEXT_FACTORY,  
  13. "weblogic.jndi.WLInitialContextFactory");  
  14.         table.put(Context.PROVIDER_URL,"t3://localhost:7001");  
  15.         //查询服务器中的jndiName  
  16.         Context context = new InitialContext(table);  
  17.         Convert convert = ( Convert) context.lookup(jndiName);  
  18.         String rmb = convert.getRmb(usd);  
  19.         System.out.println(rmb);  
  20.     }  

运行,显示的效果如图17-21所示:

 
图17-21 显示效果

说明可以正常运行。

从此处可以看出,客户端没有知道服务器端的任何源代码,就可以调用服务器端的EJB对象。

17.3.7 无状态会话Bean的生命周期

接下来讲解无状态会话Bean的生命周期。限于篇幅,本节仅仅讲解无状态会话Bean的生成和消亡。

在ConvertBean.java中增加一个构造函数:

ConvertBean.java

   
   
   
   
  1. package impl;  
  2. import itf.Convert;  
  3. import javax.ejb.Remote;  
  4. import javax.ejb.Stateless;  
  5.  
  6. @Stateless (mappedName="ConvertBean")  
  7. @Remote  
  8.  
  9. public class ConvertBean implements Convert {  
  10.     public ConvertBean(){  
  11.         System.out.println("ConvertBean构造函数");  
  12.     }  
  13.     public String getRmb(String usd){  
  14.         //从数据库查询汇率,此处简化,假如汇率是6.0  
  15.         double rate = 6.0;  
  16.         double dblUsd = Double.parseDouble(usd);  
  17.         double dblRmb = dblUsd * rate;  
  18.         String rmb = String.valueOf(dblRmb);  
  19.         return rmb;  
  20.     }  

部署,然后调用TestConvert1.java,在服务器端打印的结果为:

反复运行客户端,服务器端构造函数没有调用,说明是同一个EJB对象为所有客户端服务。关于其生命周期,读者可以参考相关文档。

17.4 有状态会话Bean开发

如前所述,有状态会话Bean,可以存储用户相关信息,在服务器端,一个Bean对象只为客户服务,本节编写有状态会话Bean。

编写有状态会话Bean很简单,以上节的ConvertBean.java为例,只需将代码中的"Stateless"改为"Stateful"即可。代码为:

ConvertBean.java

   
   
   
   
  1. package impl;  
  2. import itf.Convert;  
  3. import javax.ejb.Remote;  
  4. import javax.ejb.Stateful;  
  5.  
  6. @Stateful (mappedName="ConvertBean")  
  7. @Remote  
  8.  
  9. public class ConvertBean implements Convert {  
  10.     public ConvertBean(){  
  11.         System.out.println("ConvertBean构造函数");  
  12.     }  
  13.     public String getRmb(String usd){  
  14.         //从数据库查询汇率,此处简化,假如汇率是6.0  
  15.         double rate = 6.0;  
  16.         double dblUsd = Double.parseDouble(usd);  
  17.         double dblRmb = dblUsd * rate;  
  18.         String rmb = String.valueOf(dblRmb);  
  19.         return rmb;  
  20.     }  

其中,

   
   
   
   
  1. @ Stateful (mappedName="ConvertBean")  
  2. @Remote 

表示该EJB是一个具有远程接口的有状态会话Bean。

部署,然后调用TestConvert1.java,在服务器端打印的结果为:

反复运行客户端,服务器端构造函数都有调用,效果如图17-22所示:

EJB的简单介绍和使用_第18张图片 
图17-22 显示效果

说明是一个EJB对象为相应客户端服务。不过,读者可能会提出一个问题:既然是一个EJB为一个客户服务,是否会出现大量的EJB对象消耗内存的情况呢?实际上,EJB中的"钝化"机制,会让长期不用的EJB对象,过了一段时间从内存中腾出空间,存入缓存。这是EJB的一个特性,读者可以参考相应文献。

另外,客户也可以手工让有状态会话Bean从实例池中删除。方法是:在远程接口和实现类中定义一个方法,并在实现类中为其注释为"@Remove":

Convert.java

   
   
   
   
  1. ……  
  2. public interface Convert {  
  3.     ……  
  4. public void remove();  
  5. }  
  6. ConvertBean.java  
  7. ……  
  8. public class ConvertBean implements Convert {  
  9.     ……  
  10.     @Remove  
  11.     public void remove(){  
  12.        //释放资源  
  13. }  

此后,客户端通过接口调用remove方法即可。

17.5 有配置文件的EJB

观察前面的代码,我们将JNDI名称写在了源代码中:

ConvertBean.java

   
   
   
   
  1. ……  
  2. @Stateful (mappedName="ConvertBean")  
  3. @Remote  
  4.  
  5. public class ConvertBean implements Convert {  
  6.     ……  

实际上,将该名称写在源代码中,并不是一个好的办法。由于JNDI名称对于各个厂商具有不同的写法,因此,最好的方法是将JNDI名称写在配置文件中。

首先将"@Stateful (mappedName="ConvertBean")"改为"@Stateful"。编写配置文件的方法如下:

1. 在项目的META-INF下新建ejb-jar.xml,结构如图17-23所示:

EJB的简单介绍和使用_第19张图片 
图17-23 项目结构

2. 编写ejb-jar.xml,源代码为:

ejb-jar.xml

   
   
   
   
  1. xml version="1.0" encoding="UTF-8"?> 
  2. <ejb-jar> 
  3.   <enterprise-beans> 
  4.     <session> 
  5.       <ejb-name>ConvertBeanejb-name> 
  6.       <mapped-name>ConvertBeanmapped-name> 
  7.     session> 
  8.   enterprise-beans> 
  9. ejb-jar> 

注意,文件中的"ConvertBean"中的"ConvertBean",默认和实现类的名称相同。

编写完毕,部署,同样也可以进行访问。

17.6 编写具有本地接口的EJB

上一节讲解的是含有远程接口的EJB,该EJB可以被远程调用。前面讲过,EJB的设计,不仅仅是为了提供远程调用功能,有时候,在同一个虚拟机内,将EJB实现类的功能用接口形式公布,也可以起到降低耦合性的作用。此时,该接口适合定义为本地(Local)接口。很明显,本地接口的调用比远程接口的调用,资源消耗应该少一些。

将本例中的EJB改为本地接口版本非常简单,只需要在Bean的实现类内进行改变即可,代码如下:

ConvertBean.java

   
   
   
   
  1. package impl;  
  2. import itf.Convert;  
  3. import javax.ejb.Local;  
  4. import javax.ejb.Stateless;  
  5.  
  6. @Stateless  
  7. @Local  
  8.  
  9. public class ConvertBean implements Convert {  
  10.     public String getRmb(String usd){  
  11.         //从数据库查询汇率,此处简化,假如汇率是6.0  
  12.         double rate = 6.0;  
  13.         double dblUsd = Double.parseDouble(usd);  
  14.         double dblRmb = dblUsd * rate;  
  15.         String rmb = String.valueOf(dblRmb);  
  16.         return rmb;  
  17.     }  

其中,

   
   
   
   
  1. @Stateless  
  2. @Local 

表示该EJB是一个具有本地接口的无状态会话Bean。

重新部署,我们发现,原先的TestConvert1程序将无法调用该EJB。

实际上,想要访问实现本地接口的EJB,必须让客户端和服务器运行在同一个容器中。比如,在同一个EJB容器中,被另一个EJB访问。或者,在同一个项目中,被JSP或者Servlet访问,等等。和"远程调用"相比,本地调用性能更好,但是失去了远程调用的功能。具体实现,读者可以参考相应资料。

你可能感兴趣的:(javaee)