session facadeWord文档格式.docx
《session facadeWord文档格式.docx》由会员分享,可在线阅读,更多相关《session facadeWord文档格式.docx(14页珍藏版)》请在冰豆网上搜索。
您又为什么需要它?
很多地方都有对SessionFacade模式的清楚描述,也就是[Sun2001]和[Brown2000]。
我不想照抄那里的全部内容,而打算把它的理论在此作个总结:
基本的问题是在EJB设计中,EJB客户机(例如,Servelet、Java应用程序,等等)不可直接访问Entitybean。
之所以如此,有以下几个原因:
∙当依靠RMI-IIOp进行跨越网络的调用时运行态的性能会受到极大影响。
如果客户机请求一个Entitybean去表示如包含两项数据(比方说帐户余额和帐户所有者姓名)的银行帐户,则将需要两个网络调用。
当大量属性使网络调用成倍增加时,很快这些开销就会变得非常明显。
[Monson-Haefel]中所说的批量访问器(bulkaccessors)或许是一种解决方案,所谓批量访问器,就是Entitybean上的一些方法,它们创建并返回值对象以表示Entitybean中的数据。
它事实上就是JavaVisualAge®
的CopyHelperAccessBeans采用的解决方案。
但是,它有一个令人遗憾的缺陷,就是它假设所有的请求都需要EJB中的“所有”数据,结果为用户返回了一些不必要的数据,并导致对更大的值对象进行组织和分解时产生额外开销。
∙更重要的是,如果您允许EJB客户机直接访问Entitybean,那么就要求客户机了解Entitybean的内部方法,而这已经超出了客户机的应知的范围。
例如,操作一个Entitybean需要知道所涉及到的该实体的关系(关联,继承),这样就把业务模型的所有细节不适当地暴露给了客户机。
另外,操作多个Entitybean会要求使用客户端事务?
这是另一个使事情复杂化的因素,这意味着EJB可能要被从客户机设计中除去,而不是添加上去。
大多数设计师已经发现为了在EJB设计中避免直接访问Entitybean的解决方案都可以在[Gamma]中描述的Facade中找到。
[Gamma]这样描述Facade模式:
“为子系统中的一套接口提供了一个统一的接口。
Facade定义了一个更高层次的接口,使子系统更容易使用。
”1在EJB中应用这种思想一般意味着您应该创建一个担当Facade的SessionEJB,然后把构成子系统的一套Entitybean“包装”起来。
这样,客户机就和Entitybean实现的细节分离开来了,而且不必自己管理事务管理的细节。
但问题是有很多人到此就打住了。
然后他们轻松地往下做,开始把Entitybean包装到Sessionbean中,而不考虑Facade模式所描述的其它内容以及EJB设计中由Facade模式衍生出来的问题。
这很可能是由于把得到的Facade的“二手”信息都当真,而没去研究原始模式的缘故。
如果我们确实花了些时间去理解Facade衍生的问题,我们将可以看到很多该模式所固有的其它有益的设计可能性。
Facade模式的要点
[Gamma]中描述了很多我们应该了解的Facade模式的要点。
前面几点可在Facade模式的“适用性”描述部分找到,它描述了在什么情况下您会需要应用该模式。
它们是:
“当您想为复杂的子系统提供一个简单接口时……请使用Facade模式”和“当您想把子系统分层时……请使用Facade模式。
使用Facade为每一层子系统定义一个入口点。
”2
从对Facade模式的讨论中,我们可以提炼出两个观点。
第一点是Facade应该提供子系统的一个抽象视图,而不是简单地把整个子系统本身的ApI直接包装起来。
不幸的是,我在实际中多次看到开发者创建的Sessionbean把Entitybeanhome和Entitybean对象的全部方法直接包装起来,而不提供任何额外的抽象,这是对该模式最可恶的滥用情况之一。
请记住,这种思想是想降低整个系统的复杂性,而不是把复杂性转移到另一个对象上。
第二点,也是更微妙的一点,与分层有关。
这个观点认为您可以用多重Facade来隐藏下层子系统的细节。
因此,在这里您可以这样设想,SessionFacade应该在其它Facade之上,位于最上层,是对底层业务逻辑细节的进一步抽象。
这一点很关键。
当您看完下面两条(分别出自[Gamma]中论述Facade模式的“协作”和“相关模式”部分)叙述后,就会更加清楚这一点:
∙“客户机通过把请求发送给Facade,再由Facade把请求转发给适当的子系统对象来与子系统通信。
”3
∙“facade只是对通往子系统对象的接口进行抽象以使它们更易于使用;
它不定义新功能。
”4
我把这几点总结如下:
Facade不做系统的实际工作;
而是委托其他对象轮流做这个工作。
由此推理出您必须正确地放置这些对象,以便使该模式能按照您所期望的运行。
这一点是本模式的两种流行表达[Sun2000]和[Sun2001]之间的主要不同之处。
第一个版本,即[Sun2000],是J2EE规划的一部分,它把这种模式称为“SessionEntityFacade”。
它意在表明“为一堆企业beans提供单一的接口”。
它描述了这样一种模式,即所有的数据存取都通过Entitybean来完成,Sessionbean则为这些Entitybean提供接口。
现在的问题是[Sun2000]不一定非要以EJB为中心。
它根本不涉及其它对象类型,并且假设系统中只有EJB一类对象。
根据我的经验,我认为这会导致根本不能在工程间重用的臃肿的Session对象,而且,在同一个工程内,当需求有一点不同时就会出现问题。
现在,[Sun2001]则更通用,也没有上述问题的困扰。
它简单地把这种模式称为“SessionFacade”。
它的解决方案规定您应该“把Sessionbean当作facade来用,以封装参与工作流的业务对象之间的交互操作的复杂性”。
它根本不限制您的业务对象应该为EJB,因此是一个更加灵活的方法。
SessionFacade的重要规则
那么我们该如何应用这些关于针对会话的Facade的规则呢?
这对我们的EJB设计又意味着什么呢?
我在设计SessionFacade时遵循三条基本原则:
∙它们自己不做实际工作;
它们委派其它对象做实际工作。
这意味着Sessionfacade中的每个方法都应该很小(异常处理逻辑不计算在内,代码应为五行或更少)。
∙它们提供简单的接口。
这意味着facade方法的数量应相对较少(每个Sessionbean中仅有约24个)。
∙它们是底层系统的客户端接口。
它们应该把特定于子系统的信息封装起来,并且不应该在不必要的情况下公开它。
那么它的工作机制呢?
您还能代理别的哪些类型的对象呢?
这又会给您的设计带来什么好处呢?
在我的一篇早期论文和[Brown2001]这本书中,我已论述了其中一些问题,在那里可以找到一些详细信息。
但,总的来说,在我的多数EJB设计中我通常会找到以下四类对象:
∙值对象是包含了客户机所请求的数据的、可序列化的Javabean。
它包含Entitybean和其他数据源所包含的数据的一个子集。
它是SessionEJB方法的返回类型。
[EJB2.0]和[Sun2001]都描述了值对象和值对象的用途。
请注意[Fowler2001]称其为“数据传输对象”(DataTransferObjects),[Brown1999]也使用这个名称。
我个人觉得数据传输对象是描述性更好的术语,但不幸的是,Sun的术语似乎更通用。
∙对象制造厂(Factory)[Brown1999][Brown2000]负责构建值对象。
它能完成辨别不同的数据源、创建值对象的实例、填充值对象的实例等等工作。
每个factory类都可以从多个数据源中检索数据或更新其中的数据。
在您的对象模型中,每个“根”对象都应该有一个factory类。
(根对象是那些“包含”其它对象的对象。
)从某种意义上说,对象Factory类在JDBC或持久的Entitybean子系统上担当Facade,实现[Gamma]中提到的分层原则。
∙EntityEJB应该是标准的、企业全局范围内可用的“数据源”。
Entitybean不应包含特定于应用程序的域逻辑,也不应限制为只能在单一应用程序内工作。
请注意Entitybean是可选的,它不是这种体系结构中必需的部分;
Factory可能像JMS队列或JDBC连接那样简单地直接从数据源获取数据。
∙Action对象是Sessionbean可能调用的唯一对商业业务进行处理的对象。
Action对象只处理与简单的创建、读取、更新或删除数据无关的商业流程。
和对象Factory一样,Action对象也充当内层Facade。
一个EJB对象示例
描述类似这样的模式遇到的一个问题是,能够使用这种模式的示例都太大,以至于无法包含在模式自身的描述中。
尽管如此,我还是要尝试举出如下示例(它显然很简单)来说明一下这些对象看起来是什么样子。
假设我们正在为银行构建一个ATM系统。
这是最老掉牙的OO设计问题之一,当然其它很多书籍和论文已经讨论过它,但它确实有足够符合我们要求的有趣特点。
通过分析,我们发现了两种EJB。
∙从ATM到银行的连接表示为Sessionbean。
该bean上有一些方法负责处理您通过ATM可以完成的交易?
存款、取款以及帐户间的资金转移。
∙帐户表示为Entitybean(我们的示例采用CMp,但它在我们的示例中实际上并没什么影响)表示。
它有返回帐户余额、对帐户进行借贷处理的方法。
ATMSessionbean的远程接口如下:
packagecom.ibm.bankexample.ejbs;
importcom.ibm.bankexample.domain.*;
/**
*ThisistheEnterpriseJavaBeanRemoteInterface
*fortheATMexample.
*/
publicinterfaceATMextendsjavax.ejb.EJBObject{
voiddeposit(java.lang.StringaccountNumber,doubleamount)
throwsjava.rmi.RemoteException,
com.ibm.bankexample.domain.FactoryException;
java.util.VectorgetAccounts(java.lang.Stringuserid)
voidtransfer(java.lang.StringfromAccount,java.lang.StringtoAccount,doubleamount)
com.ibm.bankexample.domain.InsufficientFundsException,
voidwithdraw(java.lang.StringaccountNumber,doubleamount)
}
同样地,帐户EJB的远程接口如下:
*fortheAccountEntityEJB.
*/
publicinterfaceAccountextendsjavax.ejb.EJBObject{
voiddeposit(doubleamount)throwsjava.rmi.RemoteException;
java.lang.StringgetAccountNumber()throwsjava.rmi.RemoteException;
doublegetBalance()throwsjava.rmi.RemoteException;
java.lang.StringgetUserid()throwsjava.rmi.RemoteException;
voidsetBalance(doublenewValue)throwsjava.rmi.RemoteException;
voidsetUserid(java.lang.StringnewUserid)throwsjava.rmi.RemoteException;
voidwithdraw(doubleamount)throwsjava.rmi.RemoteException;
现在,我们还发现有另外两种对象类型对我们的系统是有用的。
第一种是描述显示在ATM机上的帐户信息的值对象。
这个类看起来如下所示:
publicclassAccountValueimplementsjava.io.Serializable{
privatejava.lang.StringaccountNumber;
privatedoublebalance;
当然,AccountValue类也有作为属性的getter和setter的方法,但我们暂时不考虑它们。
现在,我们基本上有了足够的信息来理解ATMEJB的getAccounts()方法的实现。
这个方法的实现如下:
publicjava.util.VectorgetAccounts(Stringuserid)throwsFactoryException{
AccountFactoryfact=newAccountFactory();
Vectorresult=fact.getAccounts(userid);
returnresult;
这个方法展示了SessionFacadeEJB的方法的标准模式。
它找到合适的帮助对象(Action或Factory,在本例中是Factory),调用帮助对象上的业务方法,然后返回结果。
如这个方法所指出的,我们需要一个AccountFactory类来从Accounts构建AccountValues。
这个类的类定义如下:
publicclassAccountFactory{
privatestaticAccountHomeaccountHome=null;
AccountFactory的getAccounts(userid)方法的实现如下:
try{
Vectorvect=newVector();
AccountHomehome=getAccountHome();
EnumerationaccountRefs=home.findByUserid(userid);
while(accountRefs.hasMoreElements()){
Accountacc=(Account)accountRefs.nextElement();
AccountValuevalueObject=newAccountValue();
valueObject.setAccountNumber(
acc.getAccountNumber());
valueObject.setBalance(acc.getBalance());
vect.addElement(valueObject);
}
returnvect;
}catch(Exceptione){
thrownewFactoryException(
"
Cannotgenerateaccountsduetowrappedexception"
+e);
这个方法使用一个高速缓存的AccountHome实例,它是从以下方法中获取的:
privateAccountHomegetAccountHome(){
if(accountHome==null){
java.lang.ObjecthomeObject=getInitialContext().lookup(
"
com/ibm/bankexample/ejbs/Account"
);
accountHome=
(AccountHome)javax.rmi.portableRemoteObject.narrow(
(org.omg.CORBA.Object)homeObject,
AccountHome.class);
}catch(Exceptione){
//Errorgettingthehomeinterface
System.out.println(
Exception"
+e+"
increateTimeSheetHome()"
returnaccountHome;
正如[Brown2001]和[Gunther2000]所描述的那样,在WebSphere®
中,高速缓存EJBhome是一个极好的习惯,因为获取JNDIInitialContext和从InitialContext获取EJBHome需要一段时间。
既然您已经看到了Session、Entity和Factory如何组合在一起,那我们就来看一个Action类的示例。
在本例中,我们有一个处理从一个帐户到另一个帐户的资金转移的Transfer对象。
Transfer由ATMEJB中的transfer()方法的实现中创建,该方法的实现如下:
publicvoidtransfer(StringfromAccount,StringtoAccount,doubleamount)
throwsInsufficientFundsException,FactoryException{
Transfertrans=newTransfer();
trans.transfer(fromAccount,toAccount,amount);
请再次注意同样的流程。
不过,这个方法不必从Action对象返回值。
Transfer类的定义如下:
publicclassTransfer{
privatestaticAccountHomeaccountHome;
transfer()方法的实现如下:
publicvoidtransfer(StringfromAccount,StringtoAccount,
doubleamount)throwsInsufficientFundsException,FactoryException{
Accountfrom=getAccountHome().findByprimaryKey(
newAccountKey(fromAccount));
Accountto=getAccountHome().findByprimaryKey(
newAccountKey(toAccount));
if(from.getBalance()<
amount)
thrownewInsufficientFundsException();
to.deposit(amount);
from.withdraw(amount);
cannotperformtransfer.Nestedexceptionis"
您已经看到,Transfer对象中的transfer()方法处理以下细节:
定位两个Account实体,确保“From”帐户有足够的余额,把转移金额存入“To”帐户,从“From”帐户中提出转移金额。
同样地,您可以看到Action对象的其它方法可以实现您系统中的其它业务规则。
使用EJB对象的原因
那么为什么我们需要这第二层对象呢?
难道我们从CORBA和RMI转到EnterpriseJavaBean就使事情更简单了吗?
为什么不把所有的逻辑都放到EJB中呢?
这有几个原因。
第一个也是最重要的原因是这是一个分层应用程序。
在单个对象中放置太多工作从来不是一个好主意。
如果您用这种方式来布置由EJB调用的对象,可以带来以