第27章. Spring框架集成
Spring集成模块可轻松地移植基于Spring的项目到Seam, 并允许Spring应用程序利用Seam的关键功能,如对话和Seam更先进的持久化上下文管理。
注意! Spring 集成代码包含在jboss-seam-ioc库中。这种依赖关系需要本章涉及到的所用seam-spring集成技术。
Seam对Spring的支持,提供的能力有:
27.1. 注入Seam组件到Spring beans
使用<seam:instance/>命名空间处理器完成注入Seam组件到Spring beans 。为启用Seam命名空间处理器,Seam命名空间必须增加到Spring beans定义文件:
<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.1.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 beans 。
<seam:instance name="someComponent" id="someSeamComponentInstance"/>
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
<property name="someProperty" ref="someSeamComponentInstance">
</bean>
现在的告诫!
Seam 的设计目的是用多上下文从底层支持有状态组件模式。Spring不是。 不象Seam双向注入, Spring注入在方法调用时不会发生。 相反,注入仅发生在在Spring bean 被实例化时。所以,在bean被实例化时可用的实例与在整个bean生命周期中使用的bean实例是同一个实例。例如,如果一个Seam对话作用域(CONVERSATION-scoped)组件实例被直接注入到一个单例Spring bean,在对话结束后,这个单独Spring bean会长期维持同一个实例的一个引用。我们称这个问题为作用域阻抗(cope impedance)。Seam双向注入确保作用域阻抗(cope impedance)作为贯穿系统的一个调用流被自然管理。在Spring中,我们需要注入一个Seam组件代理,并且在代理被调用时解析这个引用。
<seam:instance/> 标签让我们自动代理组件。
<seam:instance id="seamManagedEM" name="someManagedEMComponent" proxy="true"/>
<bean id="someSpringBean" class="SomeSpringBeanClass">
<property name="entityManager" ref="seamManagedEM">
</bean>
这个例子显示了一个方法,从一个Spring bean使用一个Seam管理(Seam-managed)持久化上下文。 (有关使用Seam管理(Seam-managed)持久化上下文替代Spring OpenEntityManagerInView过滤器的更有力的方法,请看在Spring中使用Seam管理持久化上下文)
27.2. 注入Spring beans到Seam组件
注入Spring beans到Seam组件实例更容易。实际上,有两种方法:
第二个方法我们将在下节讨论。最容易的方法是通过EL访问Spring beans。
Spring的DelegatingVariableResolver是其提供来整合JSF的集成点。 这个VariableResolver 让所有Spring beans通过它们的bean id在EL被应用。你需要增加 DelegatingVariableResolver到faces-config.xml:
<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application>
然后你可以利用@In 注入Spring beans :
@In("#{bookingService}")
private BookingService bookingService;
Spring beans在EL中的使用不局限于注入这种情况。 Spring beans可以用于Seam中使用EL表达式的任何地方 :过程和页面流定义,工作内存声明(assertions)等等。
27.3. 使Spring bean成为一个Seam组件
<seam:component/>命名空间处理器可以被用来使Spring bean成为一个Seam组件。 只需放<seam:component/> 标签在你希望成为一个Seam 组件的bean的声明内:
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
<seam:component/>
</bean>
默认时, <seam:component/>会用在bean定义中提供的类和名字创建一个无状态Seam组件。有时候,例如当使用FactoryBean, Spring bean的类可以不是出现在bean定义中的类。在这种情况,类应该明确定义。在有命名冲突的情况下,Seam组件名可明确指定 。
如果你希望Spring bean被管理在一个特殊的Seam作用域,可以使用<seam:component/> 的scope 属性。如果Seam 指定的作用域不是无状态作用域, Spring bean必须用原型限定,先存在的 Spring beans通常有一个无状态字符, 因此通常不需要这个属性。
27.4. Seam作用域的Spring Bean
Seam 集成包也允许你使用Seam的上下文作为Spring 2.0类型的自定义作用域。这允许你在所有Seam 上下文中声明任何Spring bean 。然而再次告诫,Spring 组件模型绝不是被构建来支持有状态的,所以,请小心使用这个功能。特别是,会话群集或对话作用域的Spring beans是大有问题的,当从一个大的作用域注入一个bean或组件到一个小的作用域时,必须要很小心。
一旦在Spring bean factory 配置中指定了<seam:configure-scopes/>,任何Seam作用域都将会被作为Spring beans的自定义作用域来使用。为了让Spring bean与一个特殊的Seam作用域关联,用bean定义的scope 属性来指定Seam作用域:
<!—每个bean factory 只需要指定一次-->
<seam:configure-scopes/>
...
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>
通过在configure-scopes 的定义中指定prefix 属性,作用域名的前缀可以被改变。 (默认前缀是seam。)
默认时,当利用@In引用时,以这种方式注册的Spring组件的一个实例不会被自动创建。 为了让实例自动创建,你必须在注入点指定@In(create=true)来标明一个特殊的bean会被自动创建,或者利用configure-scopes的default-auto-create属性,让所有使用seam 作用域的spring beans自动创建。
用这种方式定义的Seam作用域Spring beans, 不用<seam:instance/>就可以注入到另外的Spring beans中。然而,必须注意确保作用域阻抗被维持。在Spring中使用的一般方法是在bean定义中指定 <aop:scoped-proxy/>。可是,Seam作用域的Spring beans是不兼容<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>
27.5. 使用Spring PlatformTransactionManagement
Spring提供了一个扩展事务管理抽象,支持多种事务APIs (JPA, Hibernate, JDO, 和 JTA)。 Spring还提供了与多种应用服务器的事务管理器的紧密集成,如Websphere和Weblogic。 Spring事务管理接口(exposes)支持多种高级功能,如嵌套事务,并且支持完全的Java EE事务传播规则,如REQUIRES_NEW和 NOT_SUPPORTED。更多的信息,见Spring文档。
激活SpringTransaction组件使用Spring事务 ,如下配置Seam:
<spring:spring-transaction platform-transaction-manager="#{transactionManager}"/>
spring:spring-transaction组件将利用Springs事务同步能力实现同步回调用 。
27.6. 在Spring中使用Seam管理的持久化上下文
Seam最强大的功能之一是它的对话作用域(conversation scope)和为对话存活期提供一个开放的实体管理器(EntityManager)的能力。这消除了大部分与实体关联的分离和重组问题,也减轻了恐怖的LazyInitializationException 的出现。Spring没有为超出单个网页请求的作用域提供管理持久化上下文的方法(OpenEntityManagerInViewFilter)。因此,如果Spring 开发者可以利用Spring提供的与JPA集成的完全一样的工具来访问Seam管理的持久化上下文,这将是很不错的。 (例如,PersistenceAnnotationBeanPostProcessor, JpaTemplate, 等等)。
Seam利用Spring JPA工具为Spring提供了一个访问Seam管理的持久化上下文的方法,让Spring应用程序拥有了对话作用域持久化上下文。
这个集成工作提供了下列功能:
Spring持久化上下文传播模型只允许每个EntityManagerFactory有一个开放的EntityManager,所以, Seam集成通过围绕Seam管理的持久化上下文封装了一个EntityManagerFactory进行工作。
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
<property name="persistenceContextName" value="entityManager"/>
</bean>
这里, “persistenceContextName” 是Seam管理的持久上下文组件的名字 。默认时,这个EntityManagerFactory有一个单元名字(unitName)等价于Seam组件名,本例是“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提供的任何工具中。例如,使用SpringPersistenceAnnotationBeanPostProcessor是完全与前面相同的。
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
如果你在Spring中定义了你的真实EntityManagerFactory,并只希望使用Seam管理持久化上下文 ,你可以通过用你希望使用的persistenctUnitName默认指定defaultPersistenceUnitName属性告诉PersistenceAnnotationBeanPostProcessor。
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}"/>
对Seam 管理持久化上下文,用不变的方法配置JpaTemplate和JpaDaoSupport,因为它们是在Seam 管理持久化上下文之前。
<bean id="bookingService" class="org.jboss.seam.example.spring.BookingService">
<property name="entityManagerFactory" ref="seamEntityManagerFactory"/>
</bean>
27.7. 在Spring 中使用Seam 管理的Hibernate 会话
Seam Spring集成也完全支持访问Seam 管理的Hibernate 会话, 通过使用spring工具。这个集成是非常相似于JPA integration。
正如Spring JPA集成一样,Spring传播模式只允许每事务、每EntityManagerFactory、一个开放的EntityManager可用于Spring工具。所以,Seam Session集成通过围绕一个Seam管理的Hibernate会话上下文封装一个代理SessionFactory进行工作。
<bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean">
<property name="sessionName" value="hibernateSession"/>
</bean>
在这里“sessionName”是持久化:managed-hibernate-session 组件的名字。然后,这个SessionFactory 可用于任何Spring提供的工具。该集成也提供调用SessionFactory.getCurrentInstance()的支持, 只要你调用SeamManagedSessionFactory 的getCurrentInstance()
27.8. Spring 应用上下文作为一个Seam组件
虽然可以使用Spring的 ContextLoaderListener 启动你的应用程序的Spring ApplicationContext,然而有几个局限性:
为克服这两个局限性, Spring集成包含了一个Seam组件,用于启动一个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.1.xsd
http://jboss.com/products/seam/spring
http://jboss.com/products/seam/spring-2.1.xsd">
<spring:context-loader config-locations="/WEB-INF/applicationContext.xml"/>
</components>
27.9. 为@Asynchronous使用Spring TaskExecutor
Spring提供了一个名为TaskExecutor的抽象用于异步执行代码。Spring Seam 集成允许使用 TaskExecutor来立即执行 @Asynchronous方法调用。为了激活这个功能,安装SpringTaskExecutorDispatchor ,并提供一个定义了taskExecutor 的spring bean,如下这样:
<spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}"/>
因为一个Spring TaskExecutor不支持异步事件的调度, 所以,提供了一个回调Seam分配器,可以被用来处理异步事件调度,如下这样:
<!—安装一个ThreadPoolDispatcher,处理异步事件的调度。 -->
<core:thread-pool-dispatcher name="threadPoolDispatcher"/>
<!—安装此SpringDispatcher作为默认的 -->
<spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}" schedule-dispatcher="#{threadPoolDispatcher}"/>