我们将讨论数据验证逻辑应该出现在 EJB 应用程序代码的什么位置,而不是专注于验证过程(Java 技术专区的其它地方对此进行了很好的讨论)。在本系列先前的技巧文章中,我们了解了很多组成基于 EJB 技术的应用程序的组件:底层会话 bean 及其业务接口;在实体 bean 及其客户机之间传送数据的值对象以及担任 Web 层和业务层之间的保护层的各种委派类。验证逻辑十分适合这些组件中的任何一个。实际上,您可以在多个组件中放置验证逻辑,在整个应用程序中分层次地放置它(尽管这样做是不可取的)。因此,我们在此处提出的问题是:在 EJB 应用程序的什么位置放置验证代码最有利?
数据验证的类型
要确定将验证代码放置在什么位置,第一步是了解您正在处理什么类型的验证。数据格式验证确保所有数据类型(整数、浮点数、字符串等)都是正确的。它还要确认变量都在答应值的范围之内以及实际的模式按预期的匹配。本质上,数据格式验证处理验证的任何方面,这些验证不需要应用特定业务规则
特定于业务的验证基于一组业务规则(例如,确保所提供的 ISBN 号与您数据库中的实际书籍相匹配)。它几乎总是需要对 EJB 层以及应用程序中的其它业务逻辑组件具有访问权。
数据格式验证
确定了正在处理的验证类型之后,下一步是确定放置代码的位置。在您的 EJB 应用程序中,数据格式验证逻辑可以如下进行放置:
将赋值(setter)方法放置在业务委派上。
将赋值(setter)方法放置在 bean 的远程接口上。
将赋值(setter)方法放置在 bean 的消息对象或值对象上。
对于本示例,我们将假定您正在处理一个包括业务委派的 EJB 应用程序。假如是这样,那么您应该采取某些步骤,确保所有的应用程序客户机(处于 Web 层)都在使用委派进行 bean 访问,而不是直接访问 bean。假如确实是这样,那么您可以将所有数据验证代码都安全地放置在业务委派方法中,如清单 1 所示。
清单 1. 业务委派中的数据格式验证 package com.ibm.library;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.List;
import javax.ejb.CreateException;
import javax.naming.NamingException;
public class LibraryDelegate implements ILibrary {
private ILibrary library;
public LibraryDelegate() {
init();
}
public void init() {
// Look up and oBTain our session bean
try {
LibraryHome libraryHome =
(LibraryHome)EJBHomeFactory.getInstance().lookup(
"java:comp/env/ejb/LibraryHome", LibraryHome.class);
library = libraryHome.create();
} catch (NamingException e) {
throw new RuntimeException(e);
} catch (CreateException e) {
throw new RuntimeException(e);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
// No validation required for Accessor (getter) methods
public boolean checkout(Book book) throws ApplicationException {
// No validation required here; the object type
// takes care of it
try {
return library.checkout(book);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
public boolean checkout(List books) throws ApplicationException {
// Validate list
for (Iterator i = books.iterator(); i.hasNext(); ) {
Object obj = i.next();
if !(obj instanceof Book) {
throw new ApplicationException(
ApplicationException.VALIDATION_ERROR,
"Only Books are allowed in the input list");
}
}
try {
return library.checkout(books);
} catch (RemoteException e) {
throw new ApplicationException(e);
}
}
// And so on...
public void destroy() {
// In this case, do nothing
}
}
对于数据格式验证,您希望使验证逻辑尽可能靠近客户机。数据格式验证经常触发错误页面或要求客户机重新输入格式错误的数据。在这些情况下,您希望花费最少的处理开销迅速向客户机提供反馈。通过将验证逻辑放置在业务委派中,您已经创建了最自然的错误处理方案。当客户机尝试向委派查询带有格式错误的数据时,就会触发错误,请求被直接送回客户机,并就该问题警告用户。
将验证逻辑放置在 bean 实现中会导致低效率的验证过程。错误消息将从 bean 实现传送到委派,而不是直接从委派传送到客户机,这很象 RemoteException,而不象应用程序异常。除了远程异常的代价之外,委派还将付出 JNDI 查找、RMI 流量以及(可能有)额外的业务逻辑的代价 — 花费在单个验证错误上的力气太多了!
特定于业务的验证
特定于业务的验证完全是一种不同的情形。业务验证错误通常比数据验证错误更复杂,并很少通过客户机交互获得解决。解决特定于业务的错误要求使用额外的实体和会话 bean 以及数据库访问,这些都必须通过 JNDI 和 RMI 事务进行处理。把这种验证放在业务委派上花费的开销会很大。更好的主意是将这种验证移回 EJB 层,尤其是放置到 bean 的实现类中。
在将该验证放置在应用程序的这一层时,所有 RMI 流量都应该是本地的;大多数应用程序服务器都将使用 VM 内的优化,以使 bean-到-bean 交互速度极快。您也可以避免 JNDI 访问,因为许多 bean 已经查找了相关 bean 的主(home)接口。此外,您的业务委派已经处理了所有必要的数据格式验证。
结束语
在决定将验证代码放置在哪里时,很重要的是能够分辨两种验证类型。数据验证是比业务验证简单得多的验证类型,一般的经验是使它尽可能靠近客户机。特定于业务的验证更复杂,并通常需要几种不同的事务来完成。这类验证应该放在 EJB 层,在那里,它可以尽可能地利用现有的进程。