关于SEAM

   Seam项目是由Java社区内大名鼎鼎的Gavin King,著名的开源ORM(Object/Relation Mapping)工具Hibernate的作者负责开发的。因为他在06年投入了JBoss的怀抱,所以Seam就叫JBoss Seam。又因为RedHat花3.5亿收购了JBoss,所以Seam是RedHat的开源产品。

   Seam是一个敏捷的J2EE5框架, J2EE5规范包括了JSF(Java Server Faces),EJB3(Enterprise Java Bean)和JPA(Java Persistence API)。Seam在这套核心上进行了扩展,完美的融合了许多常用的软件包。Seam意为缝,接口,接合处,所以Seam主要关心的是概念的扩展,资源的整合与封装。

   首先说一下处于最前端的JSF。它同时支持Pull和Push两种MVC模式。MVC Push代表了传统的MVC模式,MVC Pull又被称为MVC2,是下一代Java Web框架发展的趋势。

   以Struts为例说明一下Push的概念:一个请求被定向到某个Action中,Action取数据,生成一个ActionForm,返回画面,画面从ActionForm中取得数据并表示。在这个过程中,数据在画面被解析前已经准备好了,被推到了画面上,画面本身没法决定取哪些数据,只能决定显示哪些。在Push模型中,Model层一般分两部分,一个用于表示系统的内部状态,对应Struts的ActionForm,一个用来表示改变系统状态的动作,对应Struts的Action,通过ActionForm将View层和Action层分离开来,可以达到重用的目的。Push模型的缺点很明显,可能造成资源的浪费。

   要分清Pull和Push模式,就不能只看字面的意思。MVC Pull和MVC Push的本质区别并不在“推”和“拉”的不同,而在于它们背后的驱动模式。

   MVC Pull的核心是JSF,一个基于事件驱动(Event Driven)的Web模型。在JSF出现前,Java Web模型总体上是落后于基于事件驱动的.Net Web模型的。传统的MVC框架是基于表单(Form)的,画面上的一个表单对应一个事件(Action)。如果要达到Pull的要求,就需要一个更细粒度的模型,需要把页面上的每一个组件,文本框、按钮、超链接都映射到单独的事件上。这么做的目的主要是为了实现更细粒度的复用,在Push模型中,只能复用到组件,在Pull模型中,可以进一步复用到组件的每一个属性和方法。关于MVC Pull的应用,可以参考Seam发行版中Blog的例子。

   在对JSF的扩展上,Seam可以选择集成JBoss RichFaces或者ICEFaces,借此为基于Web的富客户端提供强大的组件库和便捷的AJAX支持。这个体系将传统Web开发用到的Html、Css、Javascript以及客户端与服务器的通信机制封装起来,提供更为统一、优雅的标签库,从而简化了开发。但从这段时间的使用上看,这个集成最大的意义在于富客户端组件与JSF的结合,单纯从功能性上,其与现在比较流行的Ext和YUI(Yahoo UI)还是有很大的差距。

   EJB是J2EE的核心,当然也是Seam的。以简化为原则,经过重新设计的EJB3模型,在Java社区中有相当高的评价还。而Seam之所以自称敏捷,主要就在其对EJB3模型的功能的增强和开发的简化上。

   Seam和大多数框架一样,使用上下文(Context)的概念来管理组件,所以Seam的组件模型称为上下文组件模型(Contextual Component Model),这个模型是对于EJB3模型的一个扩展。基本的Seam上下文分为七种:无状态(Stateless context)、事件(Event (or request) context)、页面(Page context)、会话(Session context)、应用(Application context)、业务会话(Conversation context)和业务流程(Business process context)。

   前五种类型在Web开发中比较常见,业务流程上下文需要和工作流引擎(BPM engine)一起使用,涉及的东西很多,都不做进一步说明了。单说一下业务会话上下文。业务会话上下文是Seam的核心概念,表示的是从用户观点出发的一组操作的集合,映射设计中的一个用例(Use Case)或者用户故事(User Story)。举个例子,在一个购物系统中,从用户选择商品开始,经过结算等一系列操作,直到付款为止,可以看作一个业务会话。Seam会在整个会话中保持组件的状态。在实现上也很简单,当触发标注有@Begin的方法后,一个流程开始,运行到标注有@End的法方后,流程终止。这个概念并不新鲜,但Seam是第一个将这个概念集成到框架中的。

   Seam的上下文是透明的,开发者一般不需要显式的调用任何与上下文相关的方法,只需要在组件上使用注释(Annotation)做出定义,组件的生命周期完全由容器来管理。SSH(Struts,Spring,Hibernate)之所以被认为是敏捷的,很大一部分原因是Spring简化了对组件,主要是对其生命周期的管理。现在,Seam可以做的更加敏捷。

   Seam的另一个创新就是双向注入(Bijection)的概念。要理解双向注入就必须先复习一下控制反转(IOC)和依赖注入(DI:Dependency Injection)。

   控制反转是一个很宽泛的定义,指程序将自己的控制权交给别人(系统,框架等),更具体的说就是你的程序写好了,什么时候调用就不由你来决定了。它源自著名的自好莱坞法则(Hollywood Principle - "Don't call us, we'll call you")。由于IOC的定义过于宽泛,在Spring一类的IOC容器出现以后,一度造成了对IOC理解的混乱。经过Java社区的争论,最终将现有的IOC容器分为两类:服务定位器(Service Locator)和依赖注入容器(DI Container)。

   服务定位器的代表就是JNDI,在这类模型中,程序运行在容器之外,显式的调用容器的方法来存取资源。程序中存在调用容器的代码,也就对容器产生依赖,并不算太优雅。于是出现了Spring这一类的DI容器。DI容器提供的是无侵入式的注入,程序运行在容器之内,资源由容器注入程序,但程序却不能向容器中反向注入。

   说到这,双向注入的概念就不难理解了,它基于注释的方式,在无侵入的情况下(注释往往不被认为是对组件的侵入),实现了容器和组件间的双向注入。在Seam中,使用@Name标注组件的唯一名称,根据名称使用@In和@Out来分别表示向内和向外的注入。再配合上边提到的上下文模型,就形成了强大的基于上下文的双向注入机制。

   Seam的DI容器还提供了很多更加便捷的功能,比如工厂方法。使用@Factory标注的方法可以作为组件的工厂方法,在上下文中没有组件实例的情况下,容器可以调用对应的工厂方法生成组件的实例。

   Seam的基于注释的注入不仅可以用于属性,还可以用于任何方法,它的实现机制更类似于Google Guice。Google的Bob Lee开发的Guice作为一个敏捷的DI容器,在Java社区的普遍评价上要优于日渐繁杂的Spring。所以在DI容器上,Seam可以说是当下Java领域内敏捷的代表。

   再看一下处在持久化层的JPA。JPA是Sun公司为统一ORM应用接口而提出的一个规范,目的就是让使用JPA的用户可以摆脱对特定ORM框架的依赖。它的接口定义和一些注释完全照搬自Hibernate。JPA的出现对于开发人员是件好事,从此,我们就可以像更换数据库一样方便的更换ORM了。但代价就是没法使用某一个ORM框架特有的功能,比如Hibernate对于原始类型集合的直接映射。

   持久化层是管理实体(Entity)的,实体就是数据的映射。Seam不仅可以管理实体的持久化与生命周期,还可以管理实体的角色。角色的概念比较特别,他是上下文模型与双向注入的产物。前边曾提到过组件名字的概念,对于一个实体,分别对应不同的上下文可以定义多个名字。比如我们用User代表一条普通的用户数据,还可以定义一个CurrentUser来表示当前登录的用户,它只出现在会话上下文中,表示了该实体在系统中的另一种角色。

   Seam与Hibernate的结合非常紧密,这就得从Seam的历史说起。下边引用J道社区中一段关于Seam历史的精辟说明:

   Seam是从Hibernate团队试图生成典型的无状态Java应用架构的挫折中成长起来的。 上一代Java应用程序的无状态特性让Hibernate团队饱受挫折,Seam吸取了他们的经验。 Seam的状态管理架构最早是用来解决持久化冲突相关问题的,特别是乐观事务处理相关的问题。可扩展的在线应用经常使用乐观事务。一个原子(database/JTA)级的事务不应该跨用户交互,除非系统设计时就是只支撑很少量的并发客户端。但几乎所有涉及到的工作都是先将数据展现给用户,没多久后更新这个数据。所以Hibernate是依据支持一种跨乐观事务的持久化上下文的思想设计的。

   不幸的是这个先于Seam和EJB3.0出现的所谓“无状态”架构并不对乐观事务进行支持。而相反,这些架构提供对于原子事务级的持久化上下文的支持。 这当然给用户带来了很多麻烦,这也是用户抱怨排名第一的Hibernate的LazyInitializationException问题的原因。 我们需要的是在应用层构建对于乐观事务的支持。

   EJB3.0认识到了此问题,并且也引入了有状态组件(有状态会话bean)的思想,它使用一个扩展持久化上下文来跟踪组件的生命周期。 这是该问题的部分解决方案(对它自身而言也是一个有用的构想),然而还有两个问题:

   有状态会话bean的生命周期必须在Web层通过代码手动管理(这是个麻烦的问题,而且实践起来比听上去更复杂)。

   在同一个乐观事务的不同有状态组件间,传播持久化上下文是可行的,但很困难。

   Seam 通过提供对话(Conversation)和对话期间的有状态Session Bean组件来解决第一个问题(大多数会话实际上在数据层支持乐观事务)。这对于很多不需要传递持久化上下文的简单应用(比如Seam的订阅演示程序)已经足够了。对于更复杂的在每一个对话中的有很多松耦合组件的应用来说,组件间传播持久化上下文就成为一个重要的问题了。 所以Seam扩展了EJB 3.0的持久化上下文管理模型,以此来提供对话作用域的扩展持久化上下文。


   首先复习一下原子级、悲观锁(Pessimistic Locking)与乐观锁(Optimistic Locking)的概念。

   原子级就是指一个操作的不可再分的最小单位,比如当用户要修改一条数据的时候,首先将数据表示在画面上,然后修改,因为在用户修改的时候,画面上表示的数据不应该被改变,所以可以说查询和接下来的更新组成了一个原子级操作。

   悲观锁表示的是对修改的保守态度,在这个前提下,当一个用户要修改数据的时候,系统会先将数据锁定(一般用SELECT…FOR UPDATE实现),从而保证了原子级事务的完整性。但问题很明显,如果一个用户在修改数据画面长时间停留(比如直接关闭画面),这条数据就一直保持着锁定的状态直到会话超时。

   乐观锁要更聪明一些,它可以在更新前检查数据是否被其他人修改来确保操作的完整性。

   Hibernate同时支持两种锁定方式,对于乐观锁机制,它还提供了基于版本和基于属性等多种实现方式。但和前文提到的懒加载机制一样,在这一过程中,Hibernate的当前会话必须一直活跃,因为实体的状态是由这个会话管理的。

   在使用广泛的SSH框架中,Spring负责组件的生命周期管理,它提供了Open Session In View(对于Hibernate)和Open EntityManager In View(对于JPA)来解决懒加载的问题,但这只能解决一次请求(Request)的问题,有时这个状态需要保存更长的时间。Gavin King在Seam的文档中也提到了这个问题,他在一个使用SSH的项目中碰到了各种Hibernate的事务问题,但这并不是Hibernate的错,而是那些框架实现机制上的限制,也因此才有了Seam。

   Seam支持直接使用Hibernate Validator来验证画面项目,在一次处理开始前,容器会首先通过实体属性上的验证注释来对项目进行验证。这样就避免了一些画面和应用多次验证的情况。

   最后简单说一下Seam框架对于其他可选组件的集成。jBPM是JBoss的工作流引擎,在Seam中同时被用于做画面流和工组流引擎。JBoss Rules是一个规则引擎,用于做权限验证以及其他的基于规则的验证。JBoss Cache是一个分布式的内存缓存,缓存页面、组件和做Hibernate的二级缓存。

   还有Seam的权限机制、异步通信与消息机制以及对于Email和PDF的标签库扩展都是非常强大的,基于篇幅的限制,就不详细介绍了,感兴趣的同学可以自己查阅Seam的文档。

你可能感兴趣的:(Hibernate,mvc,敏捷开发,jpa,seam)