Spring集成模块可以把基于Spring的项目轻松移植到Seam,并且允许Spring应用Seam的一些关键特性,例如对话(Conversation)和Seam的高级持久化上下文管理。
请注意!Spring集成代码包含在jboss-seam-ioc库中。在本章涉及的所有seam-spring集成技术中都需要引用这个依赖。
Seam对Spring提供了如下一些支持:
把Seam的组件实例注入到Spring Bean中
把Spring Bean注入到Seam组件中
把Spring Bean转换成Seam组件
允许Spring Bean存在于任何Seam的上下文中
使用Seam组件来启动一个Spring WebApplicationContext
支持Spring PlatformTransactionManagement
为Spring的 OpenEntityManagerInViewFilter 和 OpenSessionInViewFilter 提供一个Seam管理的替代品
支持由 @Asynchronous 调用的Spring TaskExecutors
要把Seam组件注入到Spring Bean中,需要使用到 <seam:instance/> 命名空间处理器。 要启用该处理器,Spring Bean的定义文件中必须添加Seam命名空间:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:seam="http://jboss.com/products/seam/spring-seam" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://jboss.com/products/seam/spring-seam http://jboss.com/products/seam/spring-seam-2.0.xsd">
现在,每一个Seam组件都可以被注入到任意Spring Bean中了:
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype"> <property name="someProperty"> <seam:instance name="someComponent"/> </property> </bean>
可以用EL表达式来代替组件名:
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype"> <property name="someProperty"> <seam:instance name="#{someExpression}"/> </property> </bean>
Seam组件实例甚至还可以通过Spring Bean id来注入到Spring Bean中。
<seam:instance name="someComponent" id="someSeamComponentInstance"/> <bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype"> <property name="someProperty" ref="someSeamComponentInstance"> </bean>
警告!
Seam使用多个上下文来完全支持有状态组件。Spring不是这样。和Seam的双向注入(bijection)不同,Spring的注入并不是在方法调用时,而是发生在Spring Bean初始化时。因此,Bean初始化时被用到的那个实例,会在Bean的整个生命周期中一直被使用。例如,一个Seam的 CONVERSATION 域组件被直接注入到一个单例Spring Bean中,这个单例的Bean将长期持有这个实例的引用,直到对话结束!我们把这种问题称为 域阻抗(scope impedance)。Seam的双向注入可以很自然地管理域阻抗,就好象系统中的调用一样。在Spring中,我们需要注入一个Seam组件的代理,并且在代理被调用的时候解析该引用。
<seam:instance/> 标签可以自动代理Seam组件。
<seam:instance id="seamManagedEM" name="someManagedEMComponent" proxy="true"/> <bean id="someSpringBean" class="SomeSpringBeanClass"> <property name="entityManager" ref="seamManagedEM"> </bean>
这个例子演示了一种在Spring Bean中使用Seam管理的持久化上下文的方法。(想要了解如何更健壮地使用Seam管理的持久化上下文来替换Spring的 OpenEntityManagerInView 过滤器,请见 在Spring中使用Seam管理的持久化上下文)
将Spring Bean注入到Seam组件实例中更容易,有二种方法:
使用EL表达式注入Spring Bean
把Spring Bean转化为Seam组件
我们将在下一小节中讨论第二种方法。访问Spring Bean最简单的方法是通过EL表达式。
Spring的DelegatingVariableResolver是Spring用于整合JSF的一个集成点。VariableResolver 允许所有的Spring Bean通过Bean id在EL中被使用。你需要在 faces-config.xml 中添加 DelegatingVariableResolver:
<application> <variable-resolver> org.springframework.web.jsf.DelegatingVariableResolver </variable-resolver> </application>
接下来你可以使用 @In 来注入 Spring Bean:
@In("#{bookingService}") private BookingService bookingService;
Spring Bean在EL中的应用不单单只有注入。Seam的任何EL表达式中都可以使用Spring Bean:过程和页面流的定义,工作内存断言(working memory assertions)等等...
<seam:component/> 命名空间处理器用于将Spring Bean转换成一个Seam组件。只需要在你希望转换为Seam组件的Bean的声明中加上 <seam:component/> 标签即可:
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype"> <seam:component/> </bean>
默认情况下, <seam:component/> 将使用Bean定义中提供的类和名称来创建一个 无状态(STATELESS) 的Seam组件。有时候,在使用 FactoryBean 时,Spring Bean的类可能不是Bean定义中的那个类。在这种情况下,class 应该是被明确指定的。在可能存在命名冲突时需要明确给出Seam的组件名。
如果你希望Spring Bean在一个特定的Seam域中受管理,就使用 <seam:component/> 的 scope 属性。如果指定了任何非 无状态 的Seam域,Spring Bean就必须限定为 prototype 的。先前存在的Spring Bean通常都有基础的无状态的特征,所以通常并不需要这个属性。
Seam集成包中同样允许你像Spring 2.0风格的自定义作用域那样来使用Seam的上下文。你可以在任意Seam上下文中定义Spring Bean。但是,需要重申的是,Spring的组件模型并非设计为支持状态(Statefullness)的,所以请小心使用这一特性。特别是Session和Conversation作用域的Spring Bean集群很有问题,从大作用域注入到小作用域时也要格外小心。
一旦在Spring的Bean Factory配置中指定了 <seam:configure-scopes/>,所有的Seam作用域都将以自定义作用域的形式暴露给Spring Bean。要将一个Spring Bean与某个特定的Seam作用域联系起来时,请在bean定义的 scope 属性中指定Seam作用域。
<!-- Only needs to be specified once per bean factory--> <seam:configure-scopes/> ... <bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>
作用域名的前缀可以通过 configure-scopes 定义的 prefix 属性来修改。(默认的前缀是seam。)
以这种方式定义的Seam作用域Spring Bean可被注入到其它Spring Bean而无需使用 <seam:instance/>。但是,仍要小心确认域阻抗是否得到维护。通常,在Spring中的一般做法是在Bean定义中指定 <aop:scoped-proxy/>。但是,Seam作用域的Spring Bean并 不 兼容于 <aop:scoped-proxy/>。所以如果你需要向某个单例中注入Seam作用域的Spring Bean,必须使用 <seam:instance/>:
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/> ... <bean id="someSingleton"> <property name="someSeamScopedSpringBean"> <seam:instance name="someSpringBean" proxy="true"/> </property> </bean>
Spring提供了支持多种事务API(JPA、Hibernate、JDO和JTA)的可扩展事务管理抽象,还提供了与诸如WebSphere和WebLogic之类的应用服务器TransactionManagers的紧密集成。Spring事务管理支持很多高级特性,例如内嵌事务和完整的Java EE事务传播规则(REQUIRES_NEW、NOT_SUPPORTED等等)。想要获得更多信息,请见Spring文档。
如下配置Seam将启用SpringTransaction组件来使用Spring事务:
<spring:spring-transaction platform-transaction-manager="#{transactionManager}"/>
spring:spring-transaction组件将利用Spring事务同步能力来同步回调。
Seam的最强大的功能之一是它的对话作用域(conversation scope)和为对话周期提供一个EntityManager的能力。这消除了很多与实体的分离和重组相关的问题,减少了 LazyInitializationException 的发生。Spring没有管理超出单个Web请求作用域的持久化上下文的方法(OpenEntityManagerInViewFilter)。所以,如果Spring开发者能够用与Spring集成JPA所用的相同工具来访问一个Seam管理的持久化上下文的话就再好不过了。(例如PersistenceAnnotationBeanPostProcessor、JpaTemplate等等。)
Seam可以让Spring利用它的JPA工具访问Seam管理的持久化上下文,这让Spring应用拥有了对话作用域的持久化上下文的能力。
该集成提供以下功能:
使用Spring提供的工具透明地访问一个Seam管理持久化上下文
在非Web请求中访问Seam会话作用域的持久化上下文(例如异步Quartz任务中)
考虑使用Seam管理的持久化上下文和Spring管理的事务(将需要手动清除缓冲的持久化上下文)
Spring的持久化上下文传播模型允许每个EntityManagerFactory仅有一个打开的EntityManager,所以Seam集成就封装一个EntityManagerFactory,其中放入Seam管理的持久化上下文。
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean"> <property name="persistenceContextName" value="entityManager"/> </bean>
'persistenceContextName'是Seam管理的持久化上下文组件的名字。默认情况下,该EntityManagerFactory有一个和Seam组件名一样的unitName,或者像例子中那样名为'entityManager'。如果你希望提供一个不同的unitName,你能够通过提供一个persistenceUnitName来实现,如下所示:
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean"> <property name="persistenceContextName" value="entityManager"/> <property name="persistenceUnitName" value="bookingDatabase:extended"/> </bean>
这个EntityManagerFactory能在任何Spring提供的工具中被使用。例如,可以像以前那样使用Spring的 PersistenceAnnotationBeanPostProcessor。
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
如果你在Spring中定义你真正的EntityManagerFactory但希望使用一个Seam管理的持久化上下文,你能够告诉 PersistenceAnnotationBeanPostProcessor 你默认希望使用哪个persistenctUnitName,可以通过指定 defaultPersistenceUnitName 来实现。
applicationContext.xml 文件可能像下面这样:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="bookingDatabase"/> </bean> <bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean"> <property name="persistenceContextName" value="entityManager"/> <property name="persistenceUnitName" value="bookingDatabase:extended"/> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"> <property name="defaultPersistenceUnitName" value="bookingDatabase:extended"/> </bean>
component.xml 文件可能像下面这样:
<persistence:managed-persistence-context name="entityManager" auto-create="true" entity-manager-factory="#{entityManagerFactory}"/>
JpaTemplate 和 JpaDaoSupport 的配置方法不变。
<bean id="bookingService" class="org.jboss.seam.example.spring.BookingService"> <property name="entityManagerFactory" ref="seamEntityManagerFactory"/> </bean>
Seam的Spring集成支持使用Spring的工具来完整访问Seam管理的Hibernate会话(Hibernate Session)。这和 JPA集成 很像。
与Spring的JPA集成一样,在Spring的工具中,Spring的传播模型只允许每个EntityManagerFactory在一个事务里拥有一个打开的EntityManager。所以Seam Session集成封装了一个代理SessionFactory,其中包含一个Seam管理的Hibernate会话上下文。
<bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean"> <property name="sessionName" value="hibernateSession"/> </bean>
'sessionName'是persistence:managed-hibernate-session 组件的名字。该SessionFactory可被用于任意Spring提供的工具中。该集成支持对 SessionFactory.getCurrentInstance() 的调用,只要调用 SeamManagedSessionFactory 的 getCurrentInstance() 方法。
尽管你可以使用Spring的ContextLoaderListener 来启动应用程序的Spring ApplicationContext,但这种做法存在一些局限。
Spring的ApplicationContext必须开始于 SeamListener 之后
要为Seam的单元和集成测试启动一个Spring ApplicationContext有些麻烦
为突破这二个局限,Spring集成包括一个启动Spring ApplicationContext的Seam组件。在 components.xml 中添加 <spring:context-loader/> 定义就能使用该组件。在 config-locations 属性中指定Spring上下文文件位置。如果需要配置多个配置文件,你可以按照标准 components.xml 的多值配置实践,把它们置于内嵌的 <spring:config-locations/> 元素中。
<components xmlns="http://jboss.com/products/seam/components" xmlns:spring="http://jboss.com/products/seam/spring" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd http://jboss.com/products/seam/spring http://jboss.com/products/seam/spring-2.0.xsd"> <spring:context-loader context-locations="/WEB-INF/applicationContext.xml"/> </components>
Spring提供了名为 TaskExecutor 的异步代码执行抽象。在调用有 @Asynchronous 的方法时,Spring Seam集成可以使用 TaskExecutor。要启用该功能,需配置 SpringTaskExecutorDispatchor 并提供一个定义了taskExecutor的Spring Bean:
<spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}"/>
因为Spring的 TaskExecutor 并不支持异步事件调度,所以可以提供一个回调的Seam Dispatcher 来处理异步事件调度:
<!-- Install a ThreadPoolDispatcher to handle scheduled asynchronous event --> <core:thread-pool-dispatcher name="threadPoolDispatcher"/> <!-- Install the SpringDispatcher as default --> <spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}" schedule-dispatcher="#{threadPoolDispatcher}"/>