[转载]在 WebSphere Studio V5.1.2 中使用代码片段进行 EJB 编程

在 WebSphere Studio V5.1.2 中使用代码片段进行 EJB 编程


EJB 为分布式计算和容器管理持久性提供了一个强有力的机制,但是其编程模型比较复杂。本文显示了如何使用 WebSphere Studio 通过生成客户端代码和降低复杂度使 EJB 编程自动化。按照这个思路,本文解释了代码是如何起作用并展示了简单便捷的最佳实践。

引言

Enterprise Java™Bean (EJB) 为分布式计算和容器管理持久性提供了一个强有力的机制,但是其编程模型比较复杂繁琐,并且难于记忆。 本文显示了如何利用 IBM® WebSphere® Studio Application Developer V5.1.2(以下简称为 Application Developer)通过生成客户端代码和降低复杂度使 EJB 编程工作自动化。

blue_rule.gif
c.gif
c.gif
u_bold.gif回页首

EJB 查找路径

代 码自动生成既能极大地提高工作效率,还通常有利于编程人员了解幕后的工作。本文将显示一些不同的手工 EJB 编程方法,您以前可能就是这么做的。不幸的是,并不是所有的 EJB 查找方法都是等价的,还有一些使用所描述的所有方法的实际生产应用程序,尽管有些并不值得推荐。下面的实例由糟糕到较好到更好逐步提高。最后,本文将介绍 有关 EJB 查找和优化的最佳且最简单的方法。

用户可以 下载实例源代码, 这是一个项目交换 ZIP 格式的文件,可以导入到 Application Developer 工作空间中。这样做可以使您能够随着本文的操作并评估样本代码。Application Developer V5.1.2 中的项目交换特性可以让用户导入本文的内容,基础产品都包括该项特性。有关如何使用早期 WebShere Studio 版本的项目交换导入向导和特性下载方面的详细信息,请参阅 共享:Eclipse 和 WebSphere Studio 新的项目交互功能。为了利用新的 Snippet 特性,用户需要安装 Application Developer V5.1.2 或者更高版本。

实例项目交换 ZIP 文件包含 4 个项目: SnippetsEAR 和 SnippetsEJB,共同构成了基于服务器的 EJB 应用程序,还有 SnippetClientEAR 和 SnippetAppClient, 它们共同构成了应用客户端,该客户端将利用第一个应用程序的 EJB。导入这两个项目后,您的 J2EE 层次的工作空间视图将如下所示:

j2eeView.gif

SnippetsEJB 项目包含一个实体 bean 和四个会话 bean。实体 bean 是一个很简易的实例,名为 Person,具有 firstName 和 lastName 属性以及一个主字段,名为 id。 Person bean 也包含一个有关本机接口的定制 create 方法,该方法可以用来进行实例查找。这 4 种会话 bean 各自演示了略微不同的方法,用来查找 Person bean 的本机接口然后调用 create 方法。

查找 1:使用 JNDI 名称

第一个实例直接查找所引用 bean 的 JNDI 名称。这演示了用户如何在没有 EJB 引用(稍后再讨论) 的情况下执行查找操作。

所有 EJB 必须与托管应用服务器的 JNDI 名称绑定。对于 EJB 规范 V1.1 或更高版本,JNDI 名称自身并不是标准 ejb-jar.xml 部署描述符的一部分,而部署描述符是 EJB 规范的一部分。相反,它存储在一个特定于厂商的文件中供 EJB 容器运行的时候使用 (应用服务器)。在 WebSphere 应用服务器中,EJB 的 JNDI 名称存储在 META-INF 目录下名为 ibm-ejb-jar-bnd.xmi 的绑定文件中。该文件等同于 ejb-jar.xml 部署描述符 (DD),并且包含了在 DD 中定义对 EJB 对象的引用。正常情况下,用户不必手动编辑该文件。可以使用下列方法添加 EJB 的 JNDI 名称:

  • 创建新 EJB 的时候,让 Application Developer 创建缺省的 JNDI 名称。
  • 利用 Deployment Descriptor Editor 设置或修改 JNDI 名称。
  • 设置 JNDI 名称作为在服务器上安装 J2EE 应用程序的一部分。

在实例中,我们将选择用第一种方法创建缺省的 JNDI 名称。这一部分不需要其他操作,这些缺省值是由 EJB 创建向导设置的。要查看 Person bean 的 JNDI 名称,在 J2EE 层次浏览窗口中双击 bean,它将转到编辑器的 Bean 页。注意页面右边的 WebSphere 绑定部分:

bindingsSection.gif

按照惯例,所有 EJB 的 JNDI 名称应该从 ejb 部分开始。

会话 bean 实现类 LookupWithJNDIBean.java 中存在下列方法。这种代码使用硬编码引用 JNDI 名称来执行 EJB 查找:

public void createNewPerson(Integer id, String firstName, String lastName) {
Object obj = null;
try {
InitialContext ctx = new InitialContext();
obj = ctx.lookup("ejb/sample/ejb/PersonHome");
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
PersonHome aPersonHome =(PersonHome) PortableRemoteObject.narrow(obj, PersonHome.class);
Person aPerson = null;
try {
aPerson = aPersonHome.create(id, firstName, lastName);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (CreateException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

上面的代码首先使用缺省的构造器创建一个新 InitialContext。构建 InitialContext,所有的属性由应用服务器在运行的时候预先设置。然后使用 InitialContext 为注册过的、JNDI 名称为 ejb/sample/ejb/PersonHome 的对象执行 JNDI 查找。在本实例中,该对象将成为 Person bean 的远程本机接口。然后必须使用 PortableRemoteObject#narrow API 映射接口。最后,利用映射后的本机接口调用我们定制创建的方法。

下面是为什么 使用以上方法查找 EJB 的原因:

  • 在 Java 代码中 JNDI 名称是硬编码,这使得 Java 代码非常不轻便。如果用户需要修改 bean 的 JNDI 名称来减少冲突,或者用户正在部署另一个应用服务器,那么必须更新和重新编译 Java 代码。这是很繁琐的而且耗费昂贵。
  • 利 用这种实施代码时只能使用“远程”接口,这给运行在相同服务器(例如相同的 EAR、Web application 等其他 EJB)上运行的客户端带来相当大的性能损失。有关详细信息,请参阅 EJB 规范中的 “Client View of an Entity”。

查找 2:分解查找

下一个实例较第一种方法稍做改进,将常用的查找代码分解成单独的方法。会话 bean 类 LookupWithServiceMethodBean.java 中演示了以下代码:

private Object lookupRemoteHome(Class aHomeClass, String jndiName) {
Object obj = null;
try {
InitialContext ctx = new InitialContext();
obj = ctx.lookup(jndiName);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return PortableRemoteObject.narrow(obj, PersonHome.class);
}

它整理了方法中的代码,该方法实际上是检索和使用 bean 的,因此使得代码不是那么混乱,可读性稍强一些,如下所示:

public void createNewPerson(Integer id, String firstName, String lastName) {
PersonHome aPersonHome = (PersonHome)lookupRemoteHome(PersonHome.class,
"ejb/sample/ejb/PersonHome");
Person aPerson = null;
try {
aPerson = aPersonHome.create(id, firstName, lastName);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (CreateException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

而且,现在可以重用该类中的查找,以查找其它的引用 bean。但是和前一个实例一样,仍然存在类似的缺点:

  • JNDI 名称是硬编码。
  • 只能使用远程接口。

查找 3: 使用 EJB 引用

本 实例显示了如何利用 EJB 引用来查找 Person bean 的客户端接口和调用定制创建方法。EJB 引用使得用户可以在代码和引用 bean 的实际 JNDI 名称之间使用额外的间接标准。为便于说明,我们将显示如何将这种模式用于本地和远程客户端视图。但是,只要有可能,还是应该使用本地客户端视图和 EJB 本地引用。

创建从会话 bean 到 Person CMP bean 的 EJB 引用:

  1. 在 J2EE 透视图中,从 J2EE 层次视图中选择会话 bean LookupWithReference。
  2. 选择 New -> Reference。
    referenceMenu.jpg
  3. 选择 EJB Reference,然后选择 Next
  4. 选择当前 EJB 项目的 Enterprise bean,然后选择 Person bean。EJB 引用名称自动地默认为 ejb/Person 。
    localRefWiz.gif
  5. 选择 Finish

将下面的代码添加到 ejb-jar.xml文件中:


ejb/Person
Entity
sample.ejb.PersonHome
sample.ejb.Person
Person

引用的 EJB 的 JNDI 名称是默认的。从 J2EE 层次视图中双击 bean LookupWithReference,然后转到编辑器的 Reference 页:

refJNDIName.gif

该 JNDI名称是与之相连 EJB 的 JNDI 名称, Person。现在有一种不用在 Java 代码中硬编码 JNDI 名称就可以查找引用的 bean 的机制。下面是在 LookupWithReferenceBean.java 类中查找实例的改进实现方法:

public void createNewPersonUsingRemote(Integer id, String firstName, String lastName) {
PersonHome aPersonHome = (PersonHome)lookupRemoteHome(PersonHome.class, "ejb/Person");
Person aPerson = null;
try {
aPerson = aPersonHome.create(id, firstName, lastName);
} catch (CreateException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Object lookupService(String referenceName) {
try {
Context aContext = new InitialContext();
return aContext.lookup("java:comp/env/" + referenceName);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

private EJBHome lookupRemoteHome(Class aHomeClass, String refName) {
Object remote = lookupService(refName);
return (EJBHome)PortableRemoteObject.narrow(remote, aHomeClass);
}

注意到我们没有从调用方传送 JNDI 名称,而是使用 EJB 引用名称 ejb/Person 来进行查找。像前面的实例一样,分解服务查找代码,以此来保留 createNewPerson 方法,这种方法确实简单实用。分解服务查找代码还可以将其重用于其他的 EJB 引用。

类似地,用户可以使用 EJB 引用向导创建 EJB 本地引用,代码如下:

private EJBLocalHome lookupLocalHome(String refName) {
return (EJBLocalHome) lookupService(refName);
}

public void createNewPersonUsingLocal(Integer id, String firstName, String lastName) {
PersonLocalHome aPersonLocalHome = (PersonLocalHome) lookupLocalHome("ejb/PersonLocal");
PersonLocal aPerson = null;
try {
aPerson = aPersonLocalHome.create(id, firstName, lastName);
} catch (CreateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

在这个实例中,由于在使用本地接口时没有进行 RMI 调用,所以没有必要将运行结果缩小到 home 类。下面是与直接使用 JNDI 名称相比,这种查找方法的两种改进:

  • 我们对 EJB 引用进行编码,与目标 bean 的 JNDI 名称相对应。这使得在 EJB 应用程序中,不必重新编译 Java 代码就可以对 JNDI 名称进行后期绑定。注意 java:comp/env/ 模式的使用,将其附加在 EJB 引用 名称的前面来构建查找串。这是个特殊的模式,EJB 容器的 Naming 服务可以使用它来按名称对实际的 JNDI 对象进行引用。
  • 第二个实例利用了目标 ( Person) EJB 的本地客户端视图。可以这样做是因为在这种情况下客户端是运行在相同服务器上的另一个 EJB。这样就减少了 RMI 传输,这是 EJB 2.0 或更高版本的一个显著优点。

这个实例中仍存在两个不足之处:

  • 难于记住 java:comp/env/ 模式,除非您经常编写这个代码。可以通过分解 lookupService() 方法和仅传递引用名称这样的措施在一定程度上缓解这个问题。
  • 必须将这种分解后的常用查找代码复制到使用这种模式的每个客户端上。

通过上述内容您看到了对 EJB 进行编码的困难之处,下一部分将显示如何提高工作效率和优化应用程序。

blue_rule.gif
c.gif
c.gif
u_bold.gif回页首

使用代码片断简化 EJB 查找

到 目前为止我们已经看到了查找 EJB 本机接口的方法,这些方法有的不是很好,有的稍好一些,但是都需要手动编码,其中比较好的解决方法也需要对 EJB 部署描述符进行修改,以增加 EJB 引用。尽管这些方法稍好一些,仍然需要用户记住要修改部署描述符。理想情况下,用户可能在需要的时候创建 EJB 引用 -- 编写一个方法,使用 EJB 引用来查找本机接口时。

这就是 EJB 代码片段在为 EJB 的本机接口调用 create 和 find 的方法更为简单的地方。 EJB 代码片段通过提供如下优点支持在 EJB 引用上的构建:

  • 在为查找本机接口增添所需的代码的同时创建 EJB 引用
  • 可以利用向导驱动来添加必要的查找代码,这些代码一般难于记忆
  • 使用服务定位器设计模式来简化代码
  • 本机接口的可选 EAR 级高速缓存(下面有这个主题的详细信息)。

通过大量的鼠标点击,可以插入分解好的代码,这些代码能够提供简化对本机接口的查找并调用所选的方法。下面的部分描述了一个和前面的会话 bean 类似的实例,除了要调用 EJB 代码片段框架在 LookupWithSnippets 会话 bean 类中插入必要的代码。

Snippet 视图

Application Developer 添加了 Snippet 视图,这种视图包括几个间隔,这样就可以调用多种类型的代码片段。J2EE 透视图在左下方选项卡区域有这样的视图,如下所示。要打开该视图,选择 Window => Show View => Other,然后选择 Basic => Snippets

snippet_view.gif

该视图中的所有间隔都用在 HTML 文件中,除了 EJB 间隔,它只能用于 .java 文件中,这些文件都包含在与企业应用程序项目相关的项目中。该 Snippet 视图提供了与 Eclipse 中 Java 模板支持类似的功能,只是 Snippet 视图提供了一种可视化的表示可用模板的方法。同时,被调用的代码片断 可以调用高级向导来选择对象来驱动模板,该模板能够扩大方法和字段来支持插入的代码片断。通常只能从 EJB 间隔操作中调用这些高级的代码片断支持。

使用 EJB 代码片断支持

EJB Snippet 支持使得在本机接口上调用 create 或者 find 方法变得简单了。 我们将讲述创建一个相同的 createNewPerson(...) 方法实例,这种方法是用为执行 EJB 本机查找的其它模式创建的,这些模式众所周知。

调用 EJB 创建方法 Snippet 向导

  1. 在 LookupWithSnippetsBean Java 文件上打开 Java 编辑器。
  2. 在方法主体的第一行创建下面的方法。
    public void createNewPerson(Integer id, String firstName, String lastName) {
    }

  3. 扩大 Snippet 视图的 EJB 间隔,双击 Call an EJB create method
    ejbCreateSnippet.gif此操作会启动 EJB 代码片断向导。
  4. 向 导的第一页要求用户选择正确的 EJB 引用,该引用涉及到您想调用的 EJB 本机接口。显示的树显示了工作空间(例如 EJB, Web 应用程序和应用程序客户端)内所有可用的 EJB 引用所有者。基于所选的 Java 文件,猜测可能的所有者,为这个可能的所有者设置选项。在我们的实例中,选择的是 LookupWithSnippet EJB。用户负责从适当的所有者中选择一个 EJB 引用。
    createSnippetWiz1.gif
  5. 由于 Person CMP bean 不存在 EJB 引用,因此单击 New 调用 EJB Reference 创建向导。通过这个向导创建 Person bean 的 EJB Local Reference。 正如上面的部分 Using an EJB reference 中所述。
  6. 现在新的引用会出现在树中。双击该 EJB 引用。
    snippetWizNewPersonRef.gif
  7. 现在需要转到那些显示可用创建方法的方法页面上。双击 create(Integer, String, String) 方法。
    createSnippetWizMethods.gif
  8. 最后一页让用户修改将插入到方法中的参数值的内容。默认情况下,这些参数值与方法本身的参数名称相同,但用户可以随意改动。这些输入的参数值仅仅是些简易字符串,象在页面上输入一样将他们插入到代码片段中,因此用户可以输入自己想要的任何代码。
    createSnippetWizParms.gif
  9. 单击 Finish 插入代码。
  10. 插入的代码可能会因为缺少导入语句而出现编译错误。要消除这些错误,选择 Ctrl + shift + o 组织导入语句。

EJB 创建方法代码片断向导输出

生成的代码

将下面的代码插入到光标位置。首先进行 PersonLocalHome 查找,然后调用 create(...) 方法创建一个新的 PersonLocal。这些插入的代码按照 Java 喜好定义的格式规则来编排格式。 如果代码没有按照用户的意愿进行格式编排,用户可以修改 Java 格式喜好以满足自己的需要。

PersonLocalHome aPersonLocalHome =
(PersonLocalHome) ServiceLocatorManager.getLocalHome(
STATIC_PersonLocalHome_REF_NAME,
STATIC_PersonLocalHome_CLASS);
PersonLocal aPersonLocal = null;
try {
if (aPersonLocalHome != null)
aPersonLocal = aPersonLocalHome.create(id, firstName, lastName);
} catch (CreateException ce) {
// TODO Auto-generated catch block
ce.printStackTrace();
}

为支持 PersonLocalHome 的查找,类中又新添加了两个静态字段。 第一个字段包含了 EJB 代码片段向导里选中的 EJB 引用的名称。

private final static String STATIC_PersonLocalHome_REF_NAME = 
"ejb/Person";

第二个静态字段包括了 PersonLocalHome 接口的类。

private final static Class STATIC_PersonLocalHome_CLASS = 
PersonLocalHome.class;

加上 serviceLocatorMgr.jar

代码片断中有一项应该增加一些内容,这是对 ServiceLocatorManager 类的引用。在深入研究该类的详细信息之前,我们首先研究一下 LookupSnippetsBean.java 文件如何在它的构建路径进行引用。该类 是 WebSphere Application Server JAR 的隐藏类 -- 实际上可以在位于 SnippetsEAR 项目的 serviceLocatorMgr.jar 文件找到该类。下面的步骤清楚地讲述了在 EJB Snippet 向导中单击 Finish 后,除了要插入代码片断和字段还要进行哪些操作:

向各企业应用程序项目中添加 serviceLocatorMgr.jar,当前的 Java 文件是各项目的一部分。 向当前 Java 文件的 Java 项目构建路径中增加类路径条目,它能找到 serviceLocatorMgr.jar,因此可以编译插入的代码。 在步骤 2 中修改 Java 项目文件 MANIFEST.MF,使其包含如下条目: Class-Path: serviceLocatorMgr.jar 这将确保运行的时候能引用 ServiceLocatorManager。

由于这是首次发布 Snippet 特性,对 serverLocatorMgr.jar 文件做了一些重要的更新。其中包括打开和关闭高速缓存功能选项(现在默认是关闭的),还包括为所有上下文设置默认属性。用户可以下载下面的这个更新后的 JAR 文件,并且将其应用到您的应用程序开发者的安装中。 下载 serviceLocatorMgrUpdate.zip 并在 Application Developer 的根目录的安装文件夹下解压。

ServiceLocatorManager

本文开头的样本中提出了几种进行 EJB 本机接口查找的可选方法。所有的实例都有一个共同点:他们都使用相同的基本逻辑进行查找。EJB 代码片断样本明显没有这个基本逻辑。 ServiceLocatorManager 封装了查找 EJB 主机接口所需的逻辑,并提出用于本地和远程主机接口的简单 API 函数。 查找主机接口的 API 函数如下所示:

public static EJBHome getRemoteHome(String
ejbReferenceName, Class homeClass)
public static EJBHome getRemoteHome(String ejbReferenceName, Class
homeClass, String providerURL, String nameserviceType)
public static EJBLocalHome getLocalHome(String
localEjbReferenceName, Class homeClass)
public static EJBLocalHome getLocalHome(String
localEjbReferenceName, Class homeClass, String providerURL, String
nameserviceType)

接下来我们将讨论代码片断中的 ServiceLocatorManager 内的 API 代码路径。

代码过程

  1. 首先从 ServiceLocatorManager.getLocalHome ( STATIC_PersonLocalHome_REF_NAME, STATIC_PersonLocalHome_CLASS) 开始。
  2. 它调用 getLocalHome(localEjbReferenceName, homeClass, null, null),其中最后的两个参数分别是提供者 URL 和 Name Service 类型。由于我们想使用默认的 InitialContext,所以在该实例中两参数均为 null。初始化默认的 InitialContext 时,没有其他的环境属性,除非使用 setDefaultProperties(Hashtable) 方法设置缺省的环境属性。
  3. 然后调用 getHome(localEjbReferenceName, homeClass, providerURL, nameserviceType, false), 其中最后的布尔值表明这是一个远程调用(在本实例中不是远程调用)。
  4. getHome(...) 方法决定了是否需要同步。是否需要同步测试取决于能否高速缓存返回的 Home。下面讨论高速缓存。
  5. doGetHome(...) 是一个最有意义的方法,因为大部分工作是在此处完成的。第一次检查主要是看是否已经在静态 HOMES Map 中缓存了主机接口,只有打开了高速缓存才能使用默认 Context。如果能在缓存中找到 Home,它就会返回。如果找不到,必须查找主机接口并且将其缓存(再次强调只有启用了高速缓存才可以):
    private static Object doGetHome(String ejbReferenceName, Class homeClass, 
    Context ctx, boolean isRemote, boolean useCache) {
    String homeName = null;
    Object home = null;
    if (useCache) {
    homeName = homeClass.getName();
    home = HOMES.get(homeName);
    }
    if (home == null) {
    if (isRemote)
    home = lookupRemoteHome(ejbReferenceName, homeClass, ctx);
    else
    home = lookupLocalHome(ejbReferenceName, ctx);
    if (useCache && home != null)
    HOMES.put(homeName, home);
    }
    return home;
    }

  6. 在步骤 4 中,通过调用 getInitialContext(String, String) 方法来获得 IntialContext。如果没有提供 providerURL 或者 nameserviceType,那么可以获得默认的 InitialContext。如果启用了高速缓存,可以获得缓存的默认 InitialContext:
    /**
    * Return a Context based on only the Provide URL and the InitialContext factory Type
    * environment properties that are being passed.
    * Note, if the parameters passed are null, the default Context will be
    * returned; otherwise, a new InitialContext will be created with these
    * properties.
    * @param providerURL
    * @param nameserviceType
    * @return
    *
    * @see #getInitialContext()
    * @see Context#PROVIDER_URL
    * @see Context#INITIAL_CONTEXT_FACTORY
    */
    public static Context getInitialContext(String providerURL, String nameserviceType) {
    Hashtable environment = setupEnvironmentHashtable(providerURL, nameserviceType);
    if (environment == null)
    return getInitialContext();
    return getInitialContext(environment);
    }

  7. 获得 InitialContext 之后,从步骤 5 开始调用方法进行查找。在代码片断中,由于要查找本地主机接口,所以需要调用 lookupLocalHome(...):
    private static EJBLocalHome 
    lookupLocalHome(String localEjbReferenceName,
    Context ctx) {
    if (localEjbReferenceName != null)
    return (EJBLocalHome) lookupService(localEjbReferenceName, ctx);
    return null;
    }

    然后调用下面的方法决定是否需要同步。这与步骤 4 的测试相同。 如果需要高速缓存,我们将同步进行实际的查找代码块:

    private static Object lookupService(String referenceName, Context aContext) {
    if (shouldUseHomeCache(aContext)) {
    synchronized (aContext) {
    return doLookupService(referenceName, aContext);
    }
    } else
    return doLookupService(referenceName, aContext);
    }

    然后在 doLookupService(...) 方法中进行实际的主机查找操作:

    private static Object doLookupService(String referenceName, Context aContext) {
    try {
    return aContext.lookup("java:comp/env/" + referenceName);
    } catch (NamingException e) {
    getErrorHandler().handleLookupFailure(e, referenceName);
    }
    return null;
    }

  8. 如果调用了 lookupRemoteHome(...) 方法,我们还需要调用 lookupRemoteHome(...) 方法,这和步骤 7 是类似的,差别是该方法的范围更窄,如下所示:
    private static EJBHome lookupRemoteHome(String ejbReferenceName, 
    Class homeClass, Context context) {
    if (ejbReferenceName != null && homeClass != null)
    return (EJBHome)
    PortableRemoteObject.narrow(lookupService(ejbReferenceName, context),
    homeClass);
    return null;
    }

ServiceLocatorManager 错误处理

为保持使用 ServiceLocatorManager 的代码简单,API 在他们的抛出子句中都不存在异常,这样所有的调用都不需要捕获异常,也不需要执行相同的逻辑来告诉用户出现了问题。这并不意味着 ServiceLocatorManager 捕获并忽略所有的异常。相反,它捕获异常并将对他们的处理情况传给已经注册过的 ServiceLocatorErrorHandler 实例。

ServiceLocatorErrorHandler 接口有如下两个 API。创建 InitialContext 时如果有问题调用第一个方法, 查找调用 InitialContext 不能找到主机接口时调用第二个方法:

/**/**
* @param e
* @param properties
*/
void handleInitialContextFailure(NamingException e, Hashtable properties);
/**
* @param e
* @param referenceName
*/
void handleLookupFailure(NamingException e, String referenceName);

需要在 ServiceLocatorManager 之前使用 ServiceLocatorManager 上的 API 注册 ServiceLocatorErrorHandler。使用下面的方法注册 ServiceLocatorErrorHandler。 如果发生异常时没有注册 ServiceLocatorErrorHandler,那么会构建一个默认的 ServiceLocatoryErrorHandler,它只打印对异常的栈跟踪:

/**
* Set the ServiceLocatorErrorHandler for this manager.
* @param handler
*/
public static void setErrorHandler(ServiceLocatorErrorHandler handler) {
ERROR_HANDLER = handler;
}

从应用程序客户端使用 EJB 代码片断

EJB 代码片支持功能的基本使用方法都是一样的,不管从何处调用它。但是,使用 EJB 代码片向导引用另一个企业应用程序内的 EJB 时,就会发生一些特别的变化。这里主要的差别就是用户不能创建定义了 EJB 链接的 EJB 引用,这是因为只有相同企业应用程序内的 EJB 引用才可以这样。

EJB 客户端 JAR 项目优先选择

开始之前,启动优先选择,这将允许创建 EJB 客户端 JAR 项目:

  1. 选择 Window > Preferences > J2EE
  2. 选择 Create EJB client JAR projects for new EJB projects 选项和 Use EJB Client JAR, if it exists 选项:
    ejbClientJARPref.gif
  3. 选择 OK

从应用程序客户端使用 EJB Snippet 向导

我们将创建的引用与在 Call EJB create method Snippet wizard 中使用的引用类型,只不过是从 SnippetAppClient 模块中的 snippet.example.MyMain.java 来实现。

  1. 在 snippet.example.MyMain.java 上打开一个 Java 编辑器,并将光标置于 main 方法处。
  2. 在 Snippet 视图上, 双击 Call an EJB create method 代码片段操作。
  3. 从树中选择 SnippetAppClient 然后单击 New
  4. 正如上面的部分 Using an EJB reference 中所示,这将会启动 Add EJB Reference 向导。 在第一页上选择 Enterprise bean in different EAR 选项,然后选择 Person CMP bean,单击 Finish
    appClientEJBReference.gif
  5. 用户将会看到以下的提示,为 SnippetEJB 项目创建了一个 EJB 客户端 JAR 项目。选择 Yes 创建客户端项目:
    ejbClientJARNotExistPrompt.gif
  6. 双击新的 ejb/Person 引用。
  7. 这 将使用户前进到 EJB Lookup Propertie 页面,在这里用户可以输入 Provider URL 和 Name Service Type 类,用于创建 InitialContext. 对于从应用程序客户端来查找 EJB 主机这些属性都是必要的。由于所选的 EJB 引用没有包含在 ejb-link 属性中所以会出现本页。对于这个实例,我们将选择 Use default context properties for EJB home interface lookups 选项,表明我们在与目标模块的文件相同的服务器上正运行应用程序客户端。 因而可以使用缺省的 InitialContext。否则用户将需要设置 Provider URL,这是一个面向服务提供方 ( 例如 ldap://somehost:389 ) 和 Name Service Type 的 URL,后者是初始文本工厂类的类名 ( 例如, com.ibm.websphere.naming.WsnInitialContextFactory):
    snippetLookupProps.gif
  8. 选择 Next
  9. 执行上一部分 Using an EJB reference 的步骤 7 至 10。

基于应用程序客户端的 EJB Snippet 向导输出

解释本向导的输出之前, 我们需要先说明一下步骤 5 之后的情况。此时,为 SnippetsEJB 创建了一个 EJB 客户端 JAR 项目,名为 SnippetsEJBClient。然后从 SnippetsEJB 项目复制特定于客户端的文件(EJB 客户端接口以及其引用)到 SnippetsEJBClient 项目中。然后将这个新的项目就作为一个项目用具 JAR 添加到 SnippetsEAR 中。 如果 EJB 模块对 EJB 客户端 JAR 文件的引用,必须将 EJB 客户端 JAR 文件与模块一起放在 EAR 中。用户可以在 SnippetsEAR 的 EAR Deployment Descriptor 的模块选项卡来看到,如下所示:

snippetsEJBClientInEAR.gif

必须要更新 SnippetsEJB 模块项目的 EJB 部署描述符才能从 EAR 文件获得新 SnippetsEJBClient.jar 的 ejb-client-jar 引用。

SnippetsEJBClient.jar

SnippetClientEAR 还有一个到 SnippetsEJBClient 项目的新项目实用 JAR 条目,这是因为现在 SnippetClientEAR 模块项目需要它。

snippetsEJBClientInAppClientEAR.gif

最后,需要为 SnippetsEJB 项目和 SnippetClient 项目设置 JAR 依赖性,以此来指向 SnippetsEJBClient.jar 条目。这将确保能够在两项目的 Java 构建路径中添加 SnippetEJBClient 项目,并且将 SnippetEJBClient.jar 条目添加到这两个项目中。这样就可以在运行的时候获取 SnippetsEJBClient.jar 的内容。

snippetClientJarDeps.gif

生成的代码

从 MyMain.java 文件的 Snippet 向导中选择 Finish 后, 输出结果实际上跟我们第一次运行该向导产生的结果一样。首先,将 ServiceLocatorMgr.jar 添加到 SnippetClientEAR 中,并将从 SnippetClient 模块项目为其添加 Java JAR 依赖性,这与 Addition of the serviceLocatorMgr.jar 部分中的内容是类似的。

在光标位置插入以下代码。首先进行 PersonHome 远程主机查找,然后调用 create(...) 方法创建一个新 Person:

PersonHome aPersonHome =
(PersonHome) ServiceLocatorManager.getRemoteHome(
STATIC_PersonHome_REF_NAME,
STATIC_PersonHome_CLASS);
Person aPerson = null;
try {
if (aPersonHome != null)
aPerson = aPersonHome.create(id, firstName, lastName);
} catch (CreateException ce) {
// TODO Auto-generated catch block
ce.printStackTrace();
} catch (RemoteException re) {
// TODO Auto-generated catch block
re.printStackTrace();
}

为支持 PersonHome 接口的查找功能,也有两个新的静态字段添加到类中。第一个字段包括了 EJB Snippet 向导里选中的 EJB 引用名称:

private final static String STATIC_PersonHome_REF_NAME = "ejb/Person";

第二个静态字段包括了 PersonHome 接口类:

private final static Class STATIC_PersonHome_CLASS = PersonHome.class;

如果需要使环境属性可变,用户可以修改生成的代码片断来传递一个哈希表类实例,它包括了用户希望设置的属性内容。例如,用户可以如下来更新上述的代码片断:

Hashtable properties = new Hashtable();
properties.put(Context.SECURITY_AUTHENTICATION, "strong");
PersonHome aPersonHome =
(PersonHome) ServiceLocatorManager.getRemoteHome(
STATIC_PersonHome_REF_NAME,
STATIC_PersonHome_CLASS,
properties);
Person aPerson = null;
try {
if (aPersonHome != null)
aPerson = aPersonHome.create(id, firstName, lastName);
} catch (CreateException ce) {
// TODO Auto-generated catch block
ce.printStackTrace();
} catch (RemoteException re) {
// TODO Auto-generated catch block
re.printStackTrace();
}


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


ServiceLocatorManager中的高速缓存

ServiceLocatorManager 的高速缓存概念在本文前面已经涉及到了。在解释如何启用高速缓存之前,需要简要的警告一下。在过去的 J2EE 1.2 版本中,ServiceLocator 的高速缓存非常盛行,这是因为用户可以缓存全局 JNDI 名称。本文已经描述了使用全局 JNDI 名称的不足之处,但是现在使用这种引用来查找 EJB,因此不再可能基于这个引用名称来缓存。由于引用名称局限于应用程序客户端、Web 应用程序和单独的 EJB,名称不再是全局的,因此高速缓存不太安全。有关为什么大多数服务定位器会出现故障的详细信息,请参阅 在 J2EE 1.3 中消除服务定位器工具的缓存

默认情况下,EJB Snippet 支持中的 ServiceLocatorManager 没有高速缓存功能,但是用户可以通过调用如下语句来启用缓存功能:

ServiceLocatorManager.setEnableDefaultHomeCaching(true)

那 么启用高速缓存的时候会发生什么情况呢?这时就会缓存默认的 InitialContext ,以避免每次都捕获它,并且任何通过默认的内容使所有的查找调用同步。而且,一旦找到缓存的主机实例,随后的查找都可以从缓存中获取,这种缓存访问受到同 步块的保护。只有使用默认上下文时才会进行高速缓存。使用其它上下文时,不对主机进行高速缓存。由主机接口的全限定名称来缓存各主机实例。这对大多数应用 程序都是安全的,但是有时候高速缓存不能返回正确的结果,当引用两个共享客户端接口的不同 EJB 时会出现这种情况。例如:

Client1
ejbs/A -> A
Client2
ejbs/B -> B

A
ABean
A
AHome

B
BBean
A
AHome

在这种情况下,如果启用了高速缓存,当客户端使用 ejbs/A 进行查找时,它会从 bean A 获得正确的 AHome。 但然后当客户端 2 使用 ejbs/B 查找时,该客户端就会获得缓存的 bean 的主机实例,这是因为实例首先与 AHome 类名存放在一起。这种情况比较少见,但是如果出现了,用户就不能启用高速缓存。同样,只缓存了主机 实例,而没有其他类型的引用,例如资源引用。ServiceLocatorManager 倾向于支持 EJB Snippet,这样就只能处理 EJB 引用。要清空缓存:

ServiceLocatorManager.flushCache()

关闭高速缓存,除非用户还有与主机查找相关的性能问题而且没有上面所描述的限制情况。如果同时执行多个查找,启用高速缓存将会明显地提高性能,否则系统性能增益甚微。

如果用户希望启用高速缓存,需要在特定的环境中描绘应用程序,以此确定最大的收益。许多应用服务器,包括 WebSphere Application Server,都支持服务器级的高速缓存引用,这使得在应用程序级获得的高速缓存的性能较之以前没有那么大了。


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


结束语

本 文探讨了在 EJB 应用程序中,查找企业 bean 的基本编程原则。文中向用户列举了一些不好的做法,并且解释了如何避免他们。文中对少数的查找方法进行了比较,并且解释了后一方法优于前一方法的原因。接 着,本文向用户展示了 Application Developer 中的 Snippet 视图,并演示了如何自动生成具有最优风格的代码,以及根据需要创建 "on the fly" EJB 引用。文章提出了用于通过 "service locator manager" 来执行查找的基于模式的解决方案,并解释了这些代码是如何运行的。最后,本文向用户展示了一个引用其他 EAR 文件中的 EJB 的简单方法,该方法能够引用另一 EAR 文件中的 EJB,且能自动为引用应用程序创建 EJB 客户端 JAR。使用本文中的技术会使您的开发小组得到如下好处:

  • 提高开发 EJB 应用程序的效率
  • 更好的 EJB 应用程序运行时性能
  • 简单、易读、易懂的代码
  • 适用于各种企业应用程序的良构的应用程序封装方式

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130628/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130628/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值