在J2EE项目设计与应用中,会话外观是一种普遍使用的模式。它可以使得应用更分布式,代码耦合减少,网络流量减少。总体上他给了客户端粗粒度的访问,保护了服务器端的代码。有人说会话外观模式是一个成功系统必备的设计方案,这是一点也不夸张。在一个成功的分布式应用中不使用会话外观模式简直有点不可想象。
会话外观不同于前端控制器,他有自己的应用领域,解决不同的问题。在客户端与服务器端联系时,尤其是与EJB交互时,会话外观起重要作用,会话外观可以起到降低客户端与服务器端耦合的作用,当然降低的程度取决于会话外观的实现形式。与值对象配合使用可以降低偶合,与xml配合使用甚至可以解除耦合。
下面我们举一简单的例子来看看会话外观模式是怎样体现这些优点的。考虑一个银行系统中,顾客转帐的例子。设想这样的情景:顾客在ATM对自己的帐户做远程操作,把自己的钱从储蓄帐号转到经常使用帐号。应用中,系统首先要核实客户的身份,然后从储蓄帐户消掉要转的钱,再次在经常使用增加这部分钱。我们用三个EJB来表示这核对帐户(Customer EJB),对存储帐户操作(SavingAccount EJB)和对经常帐户(CheckingAccount EJB)操作,客户端应该遵守的操作流程可以用如下的顺序图表示:
要 理解上面的操作流程,需要首先理解EJB的工作原理。本质上EJB是一个框架结构,所以理解工作原理,就是要理解这个框架的运行机理。 在EJB中有三个部分,分别供客户端与服务器端使用。比如对客户提供验证的操作的Customer EJB,他有本地接口,远程接口与服务器方方法实现类三个部分。推荐的命名方案是本地接口命名为CustomerHome,远程接口命名为Customer,服务器方方法实现类则命名为CustomerBean.在实现中,因为检验本身是一种抽象行为,要检验的是用户名、密码、开户帐号等系列信息,动作本身是与具体的客户无关的。所以他只需要是一个Stateless EJB,与具体用户无关。这样,他完全可以在容器池中有多个实例对象,实现并行对各种不同用户的核对,提高了访问速度。存储帐户EJB (SavingAccount EJB)和经常帐户EJB(CheckingAccount EJB)的没,命名规则类似,他们也是无状态的会话Bean,理由也类似。
客户端首先获得用户验证、然后可以在储蓄帐目下消去要求转帐额,再次就可以转储到经常帐目了。三个动作我们都用EJB实现。客户端依次与三个EJB交互。我们选取CheckingAccound EJB做研究对象,看看客户端是怎样与这个Bean交互的,从EJB对客户端做的响应,我们可以看到EJB的原理。客户端调用本地方法CheckingAccountHome获得对EJB的引用,Home接口会有一个或多个Creat()方法。这个接口是与CheckingAccountBean互动的,在实现中有个ejbCreateA()方法。这种互动由是通过容器实现的。Creat()方法返回的是一个远程接口,此例中就是CheckingAccount了,客户端就得到了对远程接口的引用了,在远程接口中会有一些方法的引用。客户通过这些方法,对远程操作实现业务逻辑。这些方法的实现必须在CheckingAccountBean中实现。上面的过程我们已经看到,在获得对实现方法调用前客户端不得不与远程交互两次。在核对阶段同样要两次才能取得正确的认证,储蓄帐目的操作也同样如此。明白了操作过程与EJB原理后,我们分析上述客户端调用业务逻辑的方法至少有三点缺陷:
首先,一点没有伸缩性。
其次,这种方法没有一直性的保证。
再次,业务逻辑直接暴露,安全让人担忧。
没有伸缩性是说客户不得不对每个远程的EJB做调用。上面我们看到一个简单的转帐业务逻辑就要求客户端直接与服务器端交互六次。要是有其他的逻辑或者增加一个业务方法,客户端又得重新增加多重调用。如果是不同的客户端有不同的方法,方法调用更是忙上加乱。不良的伸缩性能从这里体现。
在整个转帐过程中,实际上只是一个事务处理过程,要么转成功,要么不成功。所以必须保证saving account和CheckingAccount在一个事务处理中。但是在上述处理中,我们看到这种保证是要求在客户端进行的。为了保证两个帐户的一致性,客户端必须把对两个EJB的调用包装在一个事务处理中。由于网络的不可靠性,延迟和错误在这时很可能会发生,事务处理的时间就自然拉长了,错误的可能性就自然增加,死锁可能造成,一致性也就难以保证。
在注意到客户端对远程方法的调用,发现都是直接调用。显然,这种方法直接暴露给客户端任意调用的方式直接导致服务器端毫无隐私,显然是方法调用的安全处理所不允许的。当然在检查EJB方法调用权限的时候,EJB规范也定义了相应的角色和安全控制,虽然这种把安全问题推到部署阶段的方法是规范允许的。但是这不同于WEB应用,对于商业性非常强的应用更是要寻求更安全的措施来解决问题。
为了克服这些缺陷,我们可以引入防火墙的概念——用Session Façade模式在客户端与业务方法之间加上一层Session bean,这样客户端与远程交互就多了一层“防火墙”,可以获得所有防火墙的好处,顺序图就如下所示了:
在目前网络状况下,网络相对与计算机来说还是一个瓶颈(bottleneck),在使用了Session Façade模式后我们把六次或更多的网络连接减少到了一个。对业务方法的访问变成为本地访问。在计算机与容器的条件下,处理速度自然比网络访问要快。不同的客户都是同一的一次性调用,伸缩性可扩展性由此显现。更重要的是,Session Façade Bean包装了业务逻辑的所有方法,并集中控制事务;业务方法也不在直接暴露在客户端下,一致性和安全性都有了保障——事务处理的严格要求转由服务器端的容器来保证,无疑可靠性大大增强了。在这个银行系统应用中我们使用方法transfer()来在客户端与EJB之间传递数据,当然是通过Session Façade来传了,具体怎么传,在下面对Session Façade模式扩展的例子中会论述到。
总之,我们不用在每次传一个细粒度的方法调用了,可以一次传递所有参数给服务器端,复杂的业务逻辑方法也从暴露给客户端处理成隐藏对客户不可见。复杂性虽然推到了Session Façade Bean中,但是逻辑更独立,耦合性更小了。
在实际的应用中,我们常常使用值对象模式来代替transfer()方法。值对象在客户端与EJB层之间传递、交换数据。一个值对象实际上是一个包装了业务数据的序列化了的类,下面是此银行系统中代替transfer()方法的值对象:AccountTransferValueObject。
public class AccountTransferValueObject implements java.io.Serializable {
private String customerPK;
private String fromAccountPK;
private String toAccountPK;
private float amount;
...
public String getCustomerPK(){
return customerPK;
}
public String getFromAccountPK(){
return fromAccountPK;
}
public String getToAccountPK(){
return toAccountPK;
}
public float getTransferAmount(){
return amount;
}
public void setCustomerPK(String customerPK){
this.customerPK = customerPK;
}
public void setFromAccountPK(String fromAccountPK){
this.fromAccountPK = fromAccountPK;
}
public void setToAccountPK(String toAccountPK){
this.toAccountPK = toAccountPK;
}
public void setTransferAmount(float amount){
this.amount = amount;
}
}
有了值对象,客户端在需要传递数据时候,就把所有的数据打包在值对象中,然后通过网络发送到EJB层,经过Session Facade Bean交给业务逻辑处理。同样当业务逻辑与客户端交互时,EJB层就会创建一个值对象来包装业务方法需要传递的数据,通过Session Façade Bean传递给客户端。
待续