Chapter 23. Spring Framework集成

Spring集成模块可以把基于Spring的项目轻松移植到Seam,并且允许Spring应用Seam的一些关键特性,例如对话(Conversation)和Seam的高级持久化上下文管理。

请注意!Spring集成代码包含在jboss-seam-ioc库中。在本章涉及的所有seam-spring集成技术中都需要引用这个依赖。

Seam对Spring提供了如下一些支持:

23.1. 把Seam组件注入Spring Bean中

要把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管理的持久化上下文

23.2. 将Spring Bean注入到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)等等...

23.3. 将Spring Bean转换为Seam组件

<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通常都有基础的无状态的特征,所以通常并不需要这个属性。

23.4. Seam作用域的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>

23.5. 使用Spring PlatformTransactionManagement

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事务同步能力来同步回调。

23.6. 在Spring中使用Seam管理的持久化上下文

Seam的最强大的功能之一是它的对话作用域(conversation scope)和为对话周期提供一个EntityManager的能力。这消除了很多与实体的分离和重组相关的问题,减少了 LazyInitializationException 的发生。Spring没有管理超出单个Web请求作用域的持久化上下文的方法(OpenEntityManagerInViewFilter)。所以,如果Spring开发者能够用与Spring集成JPA所用的相同工具来访问一个Seam管理的持久化上下文的话就再好不过了。(例如PersistenceAnnotationBeanPostProcessorJpaTemplate等等。)

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}"/>

JpaTemplateJpaDaoSupport 的配置方法不变。

<bean id="bookingService" class="org.jboss.seam.example.spring.BookingService">
	<property name="entityManagerFactory" ref="seamEntityManagerFactory"/>
</bean>

23.7. 在Spring中使用Seam管理的Hibernate会话

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() 方法。

23.8. 作为Seam组件的Spring应用上下文

尽管你可以使用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>

23.9. 使用Spring TaskExecutor的@Asynchronous

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}"/>