Seam的一个显著特点是,与您在其他架构中可能习惯的相比,更多的事物被视为《组件》。实际上,您编写的几乎每个对象都将被视为Seam组件。
例如,在其他框架中,将实体对象视为组件并不常见。但在Seam中,我们通常将User视为会话作用域组件(以表示当前登录用户),将Order和Document等实体视为会话作用域组件(大多数会话都围绕某些实体实例进行)。
侦听事件的对象也是组件。与JSF一样,Seam没有《操作》类的概念。相反,它允许将事件绑定到任何Seam组件的方法。这意味着,特别是,一个Seam组件可以模拟整个会话,组件的属性持有会话状态,组件的方法定义了会话中的每个步骤的功能。(注意,这可以在纯JSF中实现,但JSF没有定义会话上下文。)
Seam的愿景是,《事件》的概念也将是一个统一的。事件可能是一个UI事件,它可能是一个Web服务请求,是长期运行的业务流程的转换事件,一个JMS消息,或者是我们尚未考虑到的其他事物。
再次像JSF一样,事件监听方法不接收任何参数。这与Swing等GUI框架或基于操作的Web框架(如Struts)形成鲜明对比,在这些框架中,某些事件对象作为参数传递给操作方法(Servlet HttpRequest就是这种模式的例子)。或者,某些其他框架会将一些线程绑定的上下文对象暴露给操作监听器。JSF和Seam都提供线程绑定的上下文作为次要机制,但在Seam的情况下,这种机制仅适用于异常情况。
JSF在这里的想法是正确的。理想情况下,整个系统的状态可以由组件表示,这些组件由容器自动组装。这从应用程序的视图中消除了事件对象或上下文对象,从而使得代码更加整洁且更自然地面向对象。在底层,JSF实现通过命名上下文变量定位管理Bean的依赖Bean,并在上下文变量为空时自动实例化所需管理Bean的新实例。
不幸的是,JSF存在三个局限性。
首先,初始化或修改上下文变量的值需要直接访问上下文对象,即FacesContext,这打破了抽象。Seam通过引入注入
来解决这个问题。
@In @Out Document document; public void updateDocument() { document = entityManager.merge(document); } @Out User user; public void login() { user = entityManager.createQuery("from User user ....") .setParameter("username", username) .setParameter("password", password) .getSingleResult(); }
其次,组件的组装(依赖注入)是在管理Bean实例化时进行的,这意味着:(a) 广泛范围内的组件(例如会话)不能引用狭窄范围内的组件(例如请求),(b) 如果我们修改上下文变量的值,已经组装的组件将不会看到新值,并继续使用过时的对象。Seam通过使组装(注入/注入)成为在组件调用时发生的过程来解决这个问题。
@Entity @Scope(REQUEST) public class OrderLine { ... } @Stateful @Scope(CONVERSATION) public class CreateOrderConversation { @In OrderLine orderLine; public addOrderLine() { order.addOrderLine(orderLine); } }
在此期间,大多数其他IoC框架也存在这两个局限性,在像Spring这样的情况下,被注入的组件被认为是无状态的,因此任何两个组件的实例都是可互换的。如果组件是有状态的,就像JSF中的那样,这就不太好了。
第三,JSF只有三个上下文(请求、会话、应用)。因此,如果我想在多个请求之间保持与特定用户相关的状态,我必须将其放在会话范围内的组件中。这使得实际上不可能使用JSF管理Bean创建一个用户可以同时做两件事的应用程序,例如在两个窗口中同时工作!这也使得你必须在完成时手动通过上下文对象(FacesContext)手动删除会话上下文变量来清理状态。
Seam是第一个尝试创建一个真正统一和统一的应用程序上下文模型的尝试。五个基本上下文是事件、会话、会话、业务流程和应用。还有一个无状态
伪上下文。
会话上下文是逻辑(由应用程序界定的)上下文,它限于特定的视图(浏览器窗口)。例如,在一个保险索赔应用程序中,用户输入是在几个屏幕上收集的,索赔对象可能是一个会话上下文组件。
业务流程上下文持有与由jBPM编排的长运行业务流程相关的状态。如果保险索赔的审查和批准涉及与多个不同用户的交互,索赔可以是一个业务流程上下文组件,并且会在每个用户交互期间可用于注入。
你可能会反对说,一个对象可能在某些时候有一个作用域,而在其他时候有另一个作用域。实际上,我认为这种情况发生的频率比你想象的要低得多,如果确实发生,Seam将支持同一类在系统中具有多个角色
的想法。
对于具有/极其/复杂工作流程的应用程序,嵌套会话和嵌套业务流程几乎是必需的,这为任意一组作用域打开了可能性。Seam目前尚未实现这一点,但Seam的上下文模型设计用于最终扩展以涵盖这类内容。
我们甚至讨论了引入更多奇特上下文的可能。事务作用域组件有意义吗?可能对于应用程序组件来说没有意义,但对于基础设施组件可能是有意义的。(是的,Seam组件模型在应用程序组件管理之外还有用途。)现在,我宁愿在没有非常明确的需要之前不添加这类东西。
到了这个阶段,你可能想知道这个上下文组件的概念究竟有什么实际意义?对我来说,有三个关键点。
首先,它允许我们直接将有状态的组件,尤其是实体豆,绑定到网页上。(请注意,如果您打算直接将实体绑定到JSF表单,您还需要一种进行验证的好方法,这就是Seam与Hibernate Validator集成的用武之地。)因此,您可以从页面、绑定到页面事件会话豆和绑定到页面的实体豆构建整个应用程序。正是这种“非分层”架构的可能性,使得Seam成为一个潜在的高效环境。(当然,如果您想自己引入额外的层,您当然可以这样做,但这并不是强加于您的。)
其次,这意味着容器(Seam)可以保证清理结束或废弃会话的状态。在废弃会话的情况下,Seam为您提供了选择:对于服务器端会话,有一个独立于会话超时的会话超时。或者,您也可以使用客户端会话。
最后,该模型允许有状态的组件以一种相对松散耦合的方式交互。组件的客户端不必了解其生命周期或与其他组件的关系。他们只需要知道它是什么类型的东西,以及它的操作。