一篇Seam的文章
原贴网址: http://www.jdon.com/jivejdon/thread/34551.html
最近注意了一下
Seam文档,牛人Gavin King的观点貌似好多都曾出现在jdon,在jdon上混得久的人也许早就注意到了,下面贴文档的一部分:
有状态Session Bean不仅可以在bean的多次调用之间保持状态,而且在多次请求之间也可以保持状态。 不由数据库保存的状态通常应该由有状态Session Bean保持。这是 Seam和其他web框架之间的一个显著的不同点。 其他框架把当前会话的信息直接保存在 HttpSession 中,而在 Seam中你应该把它们保存在有状态Session Bean的实例中,该实例被绑定到会话上下文。这可以让 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的持久化上下文管理模型,以此来提供对话作用域的扩展持久化上下文。
数据库成为了大多数企业应用的主要瓶颈,也成为了运行环境中最不具伸缩性的层。PHP/Ruby的用户会说什么都不共享(share nothing)的架构照样具有很好的伸缩性。从表面上看也许是对的,可惜我不知道是否存在这样的多用户应用,其实现是能够在 集群的 不同结点间不共享资源。这些傻瓜真正想的是“除了数据库以外什么都不共享(Share nothing except for the database)”的架构。当然,共享数据库是多用户应用伸缩性的主要问题——因此声称这样的架构具有高伸缩性是荒谬的,你可要知道它们花费了这些人的 大部分时间。
通常,几乎所有通过共享数据库做的事情并不值得这样去做。
这就是 缓存(Cache)产生的原因。嗯,当然并不只是一个 缓存。一个设计良好的 Seam应用将具有丰富的多层 缓存策略,这也影响着应用的每一层:
当然,数据库有它自己的 缓存,这是超级重要的,但是它不能像应用层的 缓存一样具有伸缩性。
对从数据库提取出的数据,你的ORM解决方案(Hibernate,或者别的JPA实现)具有两级 缓存。这是一种很强大的能力,但是经常被误用。在一个 集群环境里,保持 缓存中的数据在整个 集群中具有事务一致性,并且和数据库一致,其代价是相当昂贵的。这对于共享在多个用户间,且很少被更新的数据最有意义。在传统的无状态架构里,人们经常使用二级 缓存来保存会话状态。这种做法总是糟糕的,在 Seam中更是大错特错的。
Seam会话上下文是会话状态的 缓存。存储于会话上下文中的组件可以保持并 缓存与当前用户交互相关的状态。
特别的,Seam管理的持久化上下文(或者一个扩展受管 EJB容器持久化上下文,它与会话范围的无状态会话Bean相关)成为了当前会话中数据的 缓存。这种 缓存趋向于拥有一个相当高的命中率!Seam优化了 集群环境中受管 Seam持久化上下文的复制,也不需要保证数据库事务的一致性(乐观锁已足够),因此你不必担心这种 缓存的性能问题,除非你把成千上万个对象读取到一个单独的持久化上下文中。
应用可以在 Seam应用上下文中 缓存非事务性状态。相应的,保存在应用上下文中的状态不能被 集群中其它结点访问。
应用通过 Seam的 pojoCache 组件可以 缓存事务性状态,这个组件把JBossCache集成到了 Seam环境中。如果你在 集群模式下运行了JBossCache,那么这个状态是可以被别的结点访问的。
最后,Seam让你能够 缓存生成的JSF页面的部分内容(rendered fragments)。与ORM的二级 缓存不一样的是,当数据发生变化时,这种 缓存不能自动的失效,因此你需要写应用代码来使它显式的失效,或者设置适当的过期策略。
如要获得更多关于二级 缓存的信息,你可以参考你的ORM解决方案的文档,因为这是个极为复杂的话题。在这节中我们会直接讨论通过 pojoCache 组件使用JBossCache,或者通过 <s:cache>控制充当页片段(page fragment)缓存。
EJB 会话Bean有声明式事务管理功能。当Bean被调用时,EJB容器能够透明地开始一个事务,在调用结束时关闭此事务。 如果我们写了一个作为JSF动作监听器的会话Bean方法,我们就可以在一个事务内处理所有与此action相关的工作,并且当我们完成此动作处理时事务 必须被提交或回滚。 这是一个很棒的功能,在很多 Seam应用程序中这是必需的。
但是,此方法还是有问题。Seam应用可能无法在对会话Bean的一次方法调用请求中完成所有的数据访问。
此请求可能由几个松耦合组件处理,Web层独立地调用每一个组件。在 Seam中,Web层的一个请求对 EJB组件发起几次甚至多次调用的现象是很常见的。
视图渲染可能需要延迟关联获取(lazy fetching of associations)。
每个请求的事务量越多,当我们的应用处理大量并发请求时越可能碰到原子和隔离问题。当然,所有的写操作要在一个事务中执行。
Hibernate用户开发了 "Open Session in View" 模式来解决该问题。 在 Hibernate社区,"Open Session in View"曾经非常重要,这是因为像 Spring这样的框架使用了事务作用域持久化上下文。 所以当未获得的关联被访问时渲染视图将引起 LazyInitializationException 异常。
这 个模式通常作为一个跨越整个请求的事务来实现。 此实现方式会有几个问题,其中最严重的是只有我们提交了事务才能确认它成功完成——但在"Open Session in View"的事务提交时,视图已经完全渲染了,甚至渲染好的应答可能已经刷新到客户端。我们怎样才能通知用户他们的事务已失败呢?
Seam在解决"Open Session in View"问题时,也解决了事务隔离和关联获取问题。该方案有有两个部分:
使用使用已扩展持久化上下文,可以覆盖一个会话作用域而不是单个事务作用域。
每次请求使用两个事务;第一个从更新模型值的起始阶段到应用程序调用结束;第二个跨越渲染响应阶段。
很多应用服务器的 HttpSession 集群实 现都有问题,对绑定到Session的可变对象状态的改变只有在明确调用 setAttribute() 的时候才会被复制。 这是Bug的一个源头,这些Bug难以在开发阶段有效找出,因为它们只会在应用服务器失效切换的时候才会被发现。 而且,实际的复制信息包含了绑定到Session的所有序列化对象图,这是低效的。
当然,EJB 有状态Session Bean必须进行自动dirty checking,并进行可变状态的复制,并且 EJB 容器也应该引入优化,例如属性级别的复制。 但不幸的是,并非所有的 Seam用户都有这么好的运气,他们的环境可能并不支持 EJB 3.0。 因此,对于Session和Conversation范围内的JavaBean和Entity Bean组件,在Web容器的Session集群之上,Seam提供了额外的 集群安全的状态管理层。
|
有状态Session Bean不仅可以在bean的多次调用之间保持状态,而且在多次请求之间也可以保持状态。 不由数据库保存的状态通常应该由有状态Session Bean保持。这是 Seam和其他web框架之间的一个显著的不同点。 其他框架把当前会话的信息直接保存在 HttpSession 中,而在 Seam中你应该把它们保存在有状态Session Bean的实例中,该实例被绑定到会话上下文。这可以让 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的持久化上下文管理模型,以此来提供对话作用域的扩展持久化上下文。
数据库成为了大多数企业应用的主要瓶颈,也成为了运行环境中最不具伸缩性的层。PHP/Ruby的用户会说什么都不共享(share nothing)的架构照样具有很好的伸缩性。从表面上看也许是对的,可惜我不知道是否存在这样的多用户应用,其实现是能够在 集群的 不同结点间不共享资源。这些傻瓜真正想的是“除了数据库以外什么都不共享(Share nothing except for the database)”的架构。当然,共享数据库是多用户应用伸缩性的主要问题——因此声称这样的架构具有高伸缩性是荒谬的,你可要知道它们花费了这些人的 大部分时间。
通常,几乎所有通过共享数据库做的事情并不值得这样去做。
这就是 缓存(Cache)产生的原因。嗯,当然并不只是一个 缓存。一个设计良好的 Seam应用将具有丰富的多层 缓存策略,这也影响着应用的每一层:
当然,数据库有它自己的 缓存,这是超级重要的,但是它不能像应用层的 缓存一样具有伸缩性。
对从数据库提取出的数据,你的ORM解决方案(Hibernate,或者别的JPA实现)具有两级 缓存。这是一种很强大的能力,但是经常被误用。在一个 集群环境里,保持 缓存中的数据在整个 集群中具有事务一致性,并且和数据库一致,其代价是相当昂贵的。这对于共享在多个用户间,且很少被更新的数据最有意义。在传统的无状态架构里,人们经常使用二级 缓存来保存会话状态。这种做法总是糟糕的,在 Seam中更是大错特错的。
Seam会话上下文是会话状态的 缓存。存储于会话上下文中的组件可以保持并 缓存与当前用户交互相关的状态。
特别的,Seam管理的持久化上下文(或者一个扩展受管 EJB容器持久化上下文,它与会话范围的无状态会话Bean相关)成为了当前会话中数据的 缓存。这种 缓存趋向于拥有一个相当高的命中率!Seam优化了 集群环境中受管 Seam持久化上下文的复制,也不需要保证数据库事务的一致性(乐观锁已足够),因此你不必担心这种 缓存的性能问题,除非你把成千上万个对象读取到一个单独的持久化上下文中。
应用可以在 Seam应用上下文中 缓存非事务性状态。相应的,保存在应用上下文中的状态不能被 集群中其它结点访问。
应用通过 Seam的 pojoCache 组件可以 缓存事务性状态,这个组件把JBossCache集成到了 Seam环境中。如果你在 集群模式下运行了JBossCache,那么这个状态是可以被别的结点访问的。
最后,Seam让你能够 缓存生成的JSF页面的部分内容(rendered fragments)。与ORM的二级 缓存不一样的是,当数据发生变化时,这种 缓存不能自动的失效,因此你需要写应用代码来使它显式的失效,或者设置适当的过期策略。
如要获得更多关于二级 缓存的信息,你可以参考你的ORM解决方案的文档,因为这是个极为复杂的话题。在这节中我们会直接讨论通过 pojoCache 组件使用JBossCache,或者通过 <s:cache>控制充当页片段(page fragment)缓存。
EJB 会话Bean有声明式事务管理功能。当Bean被调用时,EJB容器能够透明地开始一个事务,在调用结束时关闭此事务。 如果我们写了一个作为JSF动作监听器的会话Bean方法,我们就可以在一个事务内处理所有与此action相关的工作,并且当我们完成此动作处理时事务 必须被提交或回滚。 这是一个很棒的功能,在很多 Seam应用程序中这是必需的。
但是,此方法还是有问题。Seam应用可能无法在对会话Bean的一次方法调用请求中完成所有的数据访问。
此请求可能由几个松耦合组件处理,Web层独立地调用每一个组件。在 Seam中,Web层的一个请求对 EJB组件发起几次甚至多次调用的现象是很常见的。
视图渲染可能需要延迟关联获取(lazy fetching of associations)。
每个请求的事务量越多,当我们的应用处理大量并发请求时越可能碰到原子和隔离问题。当然,所有的写操作要在一个事务中执行。
Hibernate用户开发了 "Open Session in View" 模式来解决该问题。 在 Hibernate社区,"Open Session in View"曾经非常重要,这是因为像 Spring这样的框架使用了事务作用域持久化上下文。 所以当未获得的关联被访问时渲染视图将引起 LazyInitializationException 异常。
这 个模式通常作为一个跨越整个请求的事务来实现。 此实现方式会有几个问题,其中最严重的是只有我们提交了事务才能确认它成功完成——但在"Open Session in View"的事务提交时,视图已经完全渲染了,甚至渲染好的应答可能已经刷新到客户端。我们怎样才能通知用户他们的事务已失败呢?
Seam在解决"Open Session in View"问题时,也解决了事务隔离和关联获取问题。该方案有有两个部分:
使用使用已扩展持久化上下文,可以覆盖一个会话作用域而不是单个事务作用域。
每次请求使用两个事务;第一个从更新模型值的起始阶段到应用程序调用结束;第二个跨越渲染响应阶段。
很多应用服务器的 HttpSession 集群实 现都有问题,对绑定到Session的可变对象状态的改变只有在明确调用 setAttribute() 的时候才会被复制。 这是Bug的一个源头,这些Bug难以在开发阶段有效找出,因为它们只会在应用服务器失效切换的时候才会被发现。 而且,实际的复制信息包含了绑定到Session的所有序列化对象图,这是低效的。
当然,EJB 有状态Session Bean必须进行自动dirty checking,并进行可变状态的复制,并且 EJB 容器也应该引入优化,例如属性级别的复制。 但不幸的是,并非所有的 Seam用户都有这么好的运气,他们的环境可能并不支持 EJB 3.0。 因此,对于Session和Conversation范围内的JavaBean和Entity Bean组件,在Web容器的Session集群之上,Seam提供了额外的 集群安全的状态管理层。