出处 (IBM DW http://www-900.ibm.com/developerWorks/java/)大砍刀
[中文JAVA技术网 2002-01-07]
王晓强(forff@sina.com)
2001 年 12 月
这篇文章试图从J2EE客户端的角度出发来介绍J2EE体系。将介绍J2EE的客户端如何对EJB进行访问。J2EE的客户端分类。最后重点分析J2EE中两种相似的客户端Stand
alone Client与J2EE Application Client。并会给出针对同一个Stateless Session Bean的这两种不同客户端的实现实例来具体说明它们的异同。
本文的所有实例都基于SUN的J2EE SDK进行发布。附录中也会对SUN的这个非常方便我们学习J2EE的开发工具集进行介绍。
J2EE客户端简介
J2EE的客户端,简单的说就是所有针对EJB而言都处于客户调用逻辑的组件与程序。因为J2EE结构的复杂性,J2EE客户端也比较多,一般分为以下五种。Stand
Alone Client,J2EE Application Client,JSP,Servlets,其它Enterprise JavaBeans(处于客户逻辑的EJB)。
这其中,大家对JSP与Servlets可能是比较熟悉的,因为现在基于J2EE的应用开发大部分是Broswer/Server模式,所以它们也是最常用的J2EE的客户端。而EJB本身起到客户端作用,我们也会经常碰到,比如在Session
Bean中调用Entity Bean中的商业方法,那么Session Bean 就是这个Entity Bean的客户端。
谈到J2EE就不能不提到EJB。EJB是J2EE结构的核心,我们在它里面实现商业逻辑,而由实现J2EE结构的服务平台提供商为我们提供J2EE
Server、EJB Container、Web Container,从而为我们提供诸如安全控制、事务处理、客户连接、以及数据库访问这些服务。这样通过对整个体系划分出不同的角色(如应用开发者, J2EE服务器提供商等等),让我们这些开发者可以专心于商业逻辑的实现,并能最大限度的实现代码的可复用性。
在EJB1.1规范中有两种EJB,一种是Session Bean另一种是Entity Bean。这两种EJB也是我们最常用到的。不论是Session
Bean 还是Entity Bean它们在实现上都是由三部分组成。首先是Remote Home Interface,在这个远程接口中定义的是可由客户端调用的创建、查找(对Entity
Bean而言)EJB的方法。然后是Remote Interface,这个远程接口中定义的是可供客户端访问的商业方法。最后是Bean Class,这个类对客户端而言是不可访问的,在这个类中我们要具体实现相应的商业方法,以及一些供EJB
Container调用的方法。对Session Bean 与Entity Bean,以及对Entity Bean 中的bmp模式以及cmp模式而言,这个类会有很大的不同。但这不是这篇文章要重点介绍的,你只要记住对J2EE的客户端,能看到的只是Remote
Interface与Remote Home Interface这两个接口而已。下面让我们看看客户端是如何具体完成对EJB的访问的。
客户端如何访问EJB
不论是那种J2EE的客户端,它要调用一个EJB的相应商业方法都要经过以下这三步:
1、 通过JNDI定位EJB的Remote Home Interface
创建JNDI名称环境,通过在发布时你给EJB定义的JNDI名称找到该EJB的Remote Home Interface。
2、 创建EJB的实例,得到Remote Interface
调用上一步得到的Remote Home Interface中的create()方法,EJB Container会创建相应EJB的实例。而你得到的就是一个定义了EJB要实现的商业方法的Remote
Interface。(EJB Container创建EJB实例的基本过程如下:客户端调用create()方法-'EJB Container实例化相应的EJB
Bean Class'EJB Container调用Bean Class中的ejbCreate方法'最后返回给我们的是EJB的Remote
Interface)
3、 调用Remote Interface中的商业方法
客户端调用上一步创建的Remote Interface中的商业方法,EJB Container就会调用相应Bean Class实例中的相应方法。
下面让我们通过具体的代码来说明这个过程。首先我们先来创建一个Stateless Session Bean。这个Session Bean实现的功能很简单,在其中的商业方法只有一个sayHello方法,打印一句"Hello
World:"。如我上边所介绍的这个EJB共有三个部分,它们的源代码如下所示:
包括create()方法的Remote Home Interface接口:
package com.javausr.example;
import java.rmi.*;
import javax.ejb.*;
public interface HelloWorldHome extends EJBHome {
public HelloWorld create() throws RemoteException, CreateException;
}
定义商业方法的Remote Interface接口:
package com.javausr.example;
import java.rmi.*;
import javax.ejb.*;
public interface HelloWorld extends EJBObject {
public void sayHello() throws RemoteException;
}
实现商业方法的Bean Class :
package com.javausr.example;
import java.rmi.*;
import javax.ejb.*;
public class HelloWorldBean implements SessionBean {
private SessionContext sessionContext;
public void ejbCreate() {}
public void ejbRemove() throws RemoteException {}
public void ejbActivate() throws RemoteException {}
public void ejbPassivate() throws RemoteException {}
public void setSessionContext(SessionContext sessionContext) throws
RemoteException {
this.sessionContext = sessionContext;
}
/**唯一的商业方法*/
public void sayHello(){
System.out.println("Hello :)");
}
}
有了具体的EJB后,我们就可以看看在J2EE客户端是如何具体完成上边三步的。首先,我们通过JNDI得到HelloWorldHome接口,这个过程又分为以下几步:
创建一个JNDI的名称环境:
Context ctx = new InitialContext();
我这里的例子使用SUN的J2EE SDK1.3发布。在这个发布环境下,创建JNDI的名称环境不需要任何参数,因为这些参数都在系统启动时被自动设置好了。但如果你使用WEBLOGIC 或者其它的应用服务器,就需要指定Context.INITIAL_CONTEXT_FACTORY以及Context.PROVIDER_URL参数来创建这个初始的名称环境(有过JNDI的详细情况,可以参考SUN的JNDI
部分)。
通过发布时指定给EJB的JNDI名称,得到我们需要的对象:
Object ref = ctx.lookup("HelloWorld");
这里我给这个EJB起的JNDI名字是HelloWorld。
下面我们要将上一步得到的对象造型成HelloWorldHome对象:
HelloWorldHome helloWorldHome= (HelloWorldHome)PortableRemoteObject.narrow(ref,
HelloWorldHome.class);
因为得到的是远程对象,所以这里我们要用javax.rmi.PortableRemoteObject中的方法对我们得到的对象进行造型。
在我们得到了EJB的Remote Home Interface后,我们通过其中的create()方法创建一个EJB实例,并得到Remote
Interface。代码如下:
HelloWorld helloWorld = helloWorldHome.create();
现在我们就可以通过上一步得到的Remote Interface来调用我们想调用的商业方法。这里我们的EJB只实现了一个sayHello()方法。用如下的代码完成对它的调用:
helloWorld.sayHello();
所有的客户端访问EJB都是要经过以上这些步骤。Servlets一般在它的init()方法中完成创建EJB实例的步骤。而JSP访问EJB,最好都通过Java
Beans来实现,所以一般在相应Java Bean的初始化方法中完成创建EJB的步骤。当然你也可以根据自己的需要在任意地点完成这些步骤。
这篇文章试图从客户端的角度来向大家介绍J2EE结构,而不是只对客户端进行介绍。所以在这里还有一些其他的信息希望大家能了解一下。在EJB2.0规范中,有一些新的情况出现了,可以对EJB进行本地访问(Local
Access)。实现了本地访问的EJB像实现了远程访问的EJB一样有两个接口供客户端访问。一个是Local Interface它像Remote
Interface一样定义要实现的商业逻辑。一个是Local Home Interface,它提供创建EJB以及查找(对Entity Bean而言)方法,就像Remote
Home Interface一样。这两个Local Interface是供客户端访问而用,而对EJB的实现仍然在Bean Class中。对客户端而言,如果它要访问一个只有本地接口的EJB,那么它必须要和这个EJB在同一个java虚拟机中。而对一个实现远程接口的 EJB,当然没有这样的限制。本地访问会提高系统性能,而且在一些场和你必须要让EJB实现本地访问(如container-managed
relationship中目标端的Entity Bean,详情请参考EJB2.0规范。一般只是在Entity Bean中实现本地访问接口)。
这样设计一个EJB时你要考虑是实现远程还是本地访问。如果你所有的组件如EJB、WEB、J2EE Client等只是发布于同一台机器上,那么你就可以选择为EJB实现本地接口(当然还有一些必须实现本地接口的情况)。如果你要考虑分布式的情况,那么实现远程访问接口是你唯一的选择。如果你的EJB使用本地访问的话,对客户端就有了一些新的变化。客户端访问实现了本地接口EJB的流程和上面介绍的访问实现远程接口EJB的过程基本一样。但在通过EJB的JNDI名称得到EJB的对象后,对其造型成相应EJB的Local
Home接口时,不需要再使用javax.rmi.PortableRemoteObject中的方法,而是直接用相应对象类型造型。假设我们上边的EJB实现的是本地访问接口,那么相应客户端访问代码如下:
Context ctx = new InitialContext();
Object ref = ctx.lookup("HelloWorld");
HelloWorldHome helloWorldHome=(HelloWorldHome)ref ;
还有在EJB2.0中出现了一种新的EJB:Message-Driven Bean。这种新的EJB类型和其他两种有很大不同。首先,它不像其它两种EJB那样有自己的供客户端调用的Remote或者Local
Interface,它只有一个Bean Class。而且客户端也无法定位及调用Message-Driven Bean中的方法。对客户端而言它是不可见的。
下面详细介绍一下J2EE中的两种客户端,Stand alone Client与J2EE Application Client。
Stand alone Client
这种客户端就如它的名字一样,它独立于J2EE,不是J2EE的组件。它一般用于测试我们开发的EJB。下面是一个具体的例子,它访问的EJB是上边的那个Stateless
Session Bean。
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import com.javausr.example.HelloWorld;
import com.javausr.example.HelloWorldHome;
public class MyStandAloneClient {
public static void main(String[] args) {
try {
//创建jndi初始化环境
Context initial = new InitialContext();
//找到ejb的Remote Home Interface
Object objref = initial.lookup("HelloWorld");
HelloWorldHome home =
(HelloWorldHome)PortableRemoteObject.narrow(objref,
HelloWorldHome.class);
//初始化ejb,得到Remote Interface
HelloWorld helloWorld = home.create();
//调用商业方法sayHello()
helloWorld.sayHello();
System.exit(0);
} catch (Exception ex) {
System.err.println("Caught an unexpected exception!");
ex.printStackTrace();
}
}
}
打开一个dos窗口,使用如下的指令编译我们的代码(请根据自己的环境对里面的参数作相应调整):
D:\j2sdkee1.3\myapp\HELLOW~1>javac -classpath .;d:\j2sdkee1.3\lib\j2ee.jar
MyStandAloneClient.java
编译完成后在运行这个例子之前,要在我们的J2EE 服务器上发布我们所实现的EJB。这里我使用的J2EE服务器是J2EE SDK 1.3,有关它的介绍请见本文的附录1--J2EE
SDK1.3介绍。如何在它上边发布HelloWorld EJB的详细步骤请见附录2--发布HelloWorld Staless Session
Bean。
在服务器已经启动,EJB已经发布的情况下,让我们来运行一下上边的例子,使用如下的命令运行:
D:\j2sdkee1.3\myapp\HELLOW~1>java -classpath ".;d:\j2sdkee1.3\lib\j2ee.jar;helloworldClient.jar"
MyStandAloneClient
这样你就可以在J2EE Server控制台上看到我们的MyStandAloneClient调用sayHello方法所打印出来的"Hello
:)"(使用j2ee -verbose启动服务器)。
J2EE Application Client
它和Stand alone Client很像,也是一个java application,但不同的是这种客户端是J2EE组件的一种。所以要想使用这种客户端,你就要用相应J2EE服务器的应用发布工具将它发布到服务器上。因为它是J2EE的组件,它与Stand
alone Client最大不同在于它可以使用J2EE提供的服务。比如它可以由J2EE统一进行安全认证,在你运行J2EE Application
Client时系统会自动弹出一个认证窗口,你输入J2EE服务器上许可的用户名和密码后才能运行这个程序,从而访问J2EE Server上的EJB。它给我们开发带来的好处是可想而知的。
还有在J2EE Application Client中,你可以通过为已有的EJB加入JNDI参考来用自己定义的JNDI名称访问EJB。例如在我的例子中是通过下面的代码来得到EJB对象的:
Object objref = initial.lookup("java:comp/env/ejb/hai");
大家也许记得在Stand alone Client例子中,我是通过"HelloWorld"JNDI名称得到同样的对象的。而这里却是"ejb/hai"。用不同的JNDI名称来访问同一个 EJB。这样做的好处就是你可以随意更改你的EJB的JNDI名称,而J2EE
Application Client程序代码不需要有任何更改,只需要修改EJB参考中的对应关系就可以了。这里是源代码:
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import com.javausr.example.HelloWorld;
import com.javausr.example.HelloWorldHome;
public class MyJ2EEAPPClient {
public static void main(String[] args) {
try {
//创建jndi初始化环境
Context initial = new InitialContext();
//找到ejb的Remote Home Interface
Object objref = initial.lookup("java:comp/env/ejb/hai");
HelloWorldHome home =
(HelloWorldHome)PortableRemoteObject.narrow(objref,
HelloWorldHome.class);
//初始化ejb,得到Remote Interface
HelloWorld helloWorld = home.create();
//调用商业方法sayHello()
helloWorld.sayHello();
System.exit(0);
} catch (Exception ex) {
System.err.println("Caught an unexpected exception!");
ex.printStackTrace();
}
}
}
你会发现在代码上除了上面提到的JNDI名称不同外,没有其他什么不同。但是我们不能像对Stand alone Client那样,编译完后就运行它,而要先创建一个J2EE
Application Client组件,并把它加入到我们的应用中。有关这个创建过程请参考文章附录3--创建J2EE Application Client组件。
创建好J2EE Application Client组件并将我们的应用发布后,现在就可以运行我们的J2EE Application Client了。这里我们要用到J2EE
SDK1.3为我们提供的一个运行J2EE Application Client的批处理命令runclient(在\bin
目录下,将它拷到含有编译后的class文件的目录中)。最后我们使用如下的命令运行:
D:\j2sdkee1.3\myapp\HELLOW~1>runclient -client helloworld.ear -name
J2EEAPPClient
这时会出现一个认证窗口,输入用户名guest和密码guest123(系统缺省提供的用户):
然后就可以在J2EE Server的控制台看到结果了,输出Hello :)。
相应的在我们运行J2EE Application Client的控制台也会在执行过程中打印出如下信息:
Initiating login ...
Username = null
Binding name:`java:comp/env/ejb/hai`
Unbinding name:`java:comp/env/ejb/hai`
从发布J2EE Application Client组件过程以及运行过程,你已经看到了它与Stand Alone Client的两个最大的不同,一个是我们重新用另一个JNDI参考名称"hai"来访问EJB,另一个是在运行的时候系统会对我们进行认证。J2EE
Application Client可以有很多实际的用途,比如可以用它在我们的系统中实现后台管理功能。
这篇文章是我自己在学习J2EE时的一些心得。对J2EE而言,核心当然还是EJB,尤其是EntityBean。希望我这篇文章能从另一个角度给大家一些帮助。如有任何问题请大家与我联系
forff@sina.com 。
附录1 J2EE SDK1.3介绍
J2EE SDK 1.3是SUN对J2EE规范的最新实现,它包含J2EE Application Server和大量其它非常有用的开发工具。它支持下面这些技术:
HTTP and HTTPS
COS naming
Enterprise JavaBeansTM (EJB) 2.0
JavaTM Servlets 2.3
JavaServer PagesTM (JSP) 1.2
JavaTM Messaging Service (JMS) 1.0.2
J2EETM Connector 1.0
JDBCTM Standard Extension 2.0
JavaTM Transaction API (JTA) 1.0
JavaMailTM 1.2
JavaTM API for XML Parsing (JAXP) 1.1
而且它还附带一个纯java的数据库系统--CloudScape。这样你很容易在一个单机系统中开发部署一套具备J2EE所有最新特性的应用,而不需要为硬盘嘎嘎响而不见系统响应而发愁(想象一下,你在一台PC上装上ORACLE,再装上一个其他商用服务器后的感觉是什么,当然你的机器要是太好那另当别论,但我的PIII600
128兆内存,惨不忍睹)。对一个刚开始接触J2EE的开发人员而言,这是一个非常好的学习工具:)。它可以让我们更专注于技术本身,而不是开发环境。
你可以从SUN上得到它。安装后,我们要做一些配置工作。在<安装路径>\bin下有很多启动命令以及工具。在这里我们要用到的有: userconfig.bat:一般我们在这里配置好环境参数。这里用到的有JAVA_HOME(顾名思义,你的JDK的安装路径)与J2EE_HOME (J2EE
SDK 1.3的安装路径)。配置好它们每次启动服务器前调用,初始化环境参数。当然你也可以用自己的批处理命令,但用系统本已提供的不是更方便吗:)。我的该文件内容如下:
set JAVA_HOME=c:\jbuilder5\jdk1.3
set J2EE_HOME=d:\j2sdkee1.3
j2ee.bat:启动J2EE Server的命令。一般在命令提示符下直接敲入,即可启动服务。在这里我们用j2ee -verbose来启动服务器,这样我们输出的信息才能在控制台上显示出来。
deploytool.bat:构造及发布j2ee应用的工具。如果你直接敲入deploytool命令系统会启动一个可视化的发布工具。
runclient.bat:一个方便我们运行J2EE Application Client的工具。
在我们配置好环境参数后,只要直接启动这些命令就可以完成相应的工作。有关J2EE SDK 1.3的详细情况还请参考它的文档(在<J2EE_HOME>\doc下)。
附录2--发布HelloWorld Staless Session Bean
下面我们来看怎么在J2EE SDK1.3中发布这个Session Bean。
第一步:创建J2EE 应用
在J2EE SDK 1.3中你是不能直接把EJB发布出去的。你需要把EJB添加到一个已有的J2EE应用中去,然后再发布。这里我们先创建一个新的名叫J2EEClientTest的应用。这个应用最终被保存为helloworld.ear。下面是具体的步骤:
1、 打开一个dos窗口,启动j2ee服务器。
j2ee -verbose (只有用这种模式启动,我们才可以看到程序中的输出)
2、 再打开一个dos窗口,启动应用发布工具:
deploytool
这个命令会启动一个如下图所示的发布工具:
3、 下面我们来创建一个新的J2EE应用。
A、 在上图所示的菜单中选择[File]--〉[New]--〉[Application]。或者直接选择窗口上的New Application快捷方式。
B、 这时会弹出一个对话窗口。在Application File Name输入框输入你保存这个应用的文件名:*.ear,我这里用的是helloworld.ear。在Application
Display Name 输入框输入这个应用的显示名称,我这里用的是J2EEClientTest。选择ok后我们就创建好了一个新的应用。
4、 下面我们在这个新应用中加入我们的EJB组件。
A、编译好我们的EJB的3个类,我们在dos窗口下用如下的命令完成编译:
D:\j2sdkee1.3\myapp\HELLOW~1>javac -classpath ;.;d:\j2sdkee1.3\lib\j2ee.jar
-d . HelloWorldHome.java HelloWorld.java HelloWorldBean.java
编译成功后的类会在当前的目录下的com\javausr\example目录中。
B、选择[File]--〉[New]--〉[EnterpriseBean](或者直接选择窗口上的New EnterpriseBean快捷方式)将会出现如下所示的New
EnterpriseBean Wizard(跳过introduction窗口):
在JAR Display Name中填入HelloWorldEJB。在下面的Contents中将我们编译好的类加入进去。
c、选择NEXT按钮。进入下一个界面,在其中将这个EJB配置为Stateless Session Bean,并为相应的部分指定已经编译好的类。最后结果如下图所示:
d、一直选择NEXT按钮直到出现Security配置窗口,如下图:
在其中选择Deployment Settings ,会弹出上图中间的那个对话窗口,在其中选择Support Client Choice选项,然后确定。这个时候我们就可以选择Finish按钮了。这样我们新的EJB就创建好了。
第二步:发布这个应用,也就是发布了这个应用中的EJB。
1、 选择[tools]--〉[Deploy]或者窗口中的Deploy的快捷方式,会弹出如下窗口:
在其中选择Return Client Jar,而下面则会缺省生成一个返回文件的名称。这个jar文件中包括了供Stand Alone Client与J2EE
Application Client访问EJB使用的一些类,是远程访问必备。选择NEXT,进入下一步。
2、 这一步我们要为EJB指定JNDI名称,以便客户端可以访问到它。如下图所示,我们输入"HelloWorld"做为JNDI名称。
现在选择Finish按钮,我们的应用就可以发布到指定的服务器上,现在可以通过客户端来访问我们的EJB了。
附录3--创建J2EE Application Client组件
J2EE Application Client也是J2EE组件中的一种,我们要运行它就要先在J2EE SDK 1.3中已有的应用中添加一个J2EE
Application Client组件。
第一步:在上一个附录中创建的应用中,加入J2EE Application Client组件。
1、 编译我们的源代码,使用如下指令:
D:\j2sdkee1.3\myapp\HELLOW~1>javac -classpath .;d:\j2sdkee1.3\lib\j2ee.jar
MyJ2EEAPPClient.java
2、 启动J2EE服务器
j2ee -verbose
3、 启动发布工具deploytool。
Deploytool
4、 选择[File]--〉[New]--〉[Application Client Compoent]或者New Application
Client Compoent的快捷方式。
5、 跳过介绍页面,在JAR File Contents中加入我们编译后的.class文件,如图所示:
在其中还要选择好J2EE Application Client要归属的应用。
6、 选择NEXT,在下面的界面中,将我们的J2EE Application Client的显示名定为J2EEAPPClient如下图:
7、继续NEXT跳过Environment Entries窗口,来到Enterprise Bean References窗口,在这个窗口中为我们创建的HelloWorld
EJB定义另一个JNDI参考。在我的J2EE Application Client中使用"ejb/hai"JNDI名称参考来访问我们已经发布的HelloWorld
EJB。如下图:
在Home Interface与Local/Remote Interface项中分别是com.javausr.example.HelloWorldHome与com.javausr.example.HelloWorld,并在下面的Deployment
Settings for ejb/hai指定我们要参考的EJB的原来的JNDI名称"HelloWorld"。现在可以选择Finish了。
第二步:发布服务,这里我们要发布已经含有J2EE Application Client组件的应用。
选择[tools]--〉[Deploy]或者窗口中的Deploy的快捷方式,会弹出如下窗口:
在其中选择Return Client Jar,而下面则会缺省生成一个返回文件的名称。这个jar文件中包括了供Stand Alone Client与J2EE
Application Client访问EJB使用的一些类,是远程访问必备。
关于作者
王晓强,万千程序开发者中的一员,并乐在其中。热爱java和linux,一直利用java和xml相关技术进行应用开发,并在这些方面积累了丰富经验。E-mail:forff@sina.com