是否可以在Spring应用程序上下文中配置OpenSessionInViewFilter,以便context:property-placeholder可用?

时间:2014-01-29 17:08:25

标签: spring-mvc dependency-injection applicationcontext open-session-in-view deployment-descriptor

初始状况

我的Web应用程序包含Maven模块 myapp-persistence (。jar), myapp-model (。jar), myapp-service (。jar)和 myapp-web (。war)以获得传统的,松散耦合的多层体系结构。所有模块由父Maven模块连接在一起,该模块仅包含父 POM ,其中包含所有子模块的通用定义。

特别是 myapp-service (。jar)和 myapp-persistence (。jar)拥有自己的可配置(!)应用程序上下文部分和所需的对象。两个jar都必须可以使用包含的变量定义进行部署,换句话说,jar不能具有变量的具体值。

myapp-service-context.xml 使用服务器URL的变量声明 solrServer bean:

<bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
    <constructor-arg value="${solr.serverUrl}" />
    <property name="connectionTimeout" value="60000"/>
    <property name="defaultMaxConnectionsPerHost" value="40"/>
    <property name="maxTotalConnections" value="40"/>
</bean>

myapp-persistence-context.xml 使用连接变量定义 dataSource

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">      
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />       
</bean>
...
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    ...
</bean>

myapp-web (。war)引用 myapp-service (。jar)和 myapp-persistence (.jar)。在 myapp-servlet.xml 中,它包含应用程序上下文部分,并提供属性文件配置声明的bean的属性值。通过 context:property-placeholder Spring在内存中创建应用程序上下文时,使用具体值初始化所有变量。

<context:property-placeholder location="classpath*:myapp-configuration.properties" />
<import resource="classpath*:myapp-persistence-context.xml"/>
<import resource="classpath*:myapp-service-context.xml"/>

对于开发配置文件,具体的 myapp-configuration.properties 可能如下所示:

solr.serverUrl=http://localhost:8983/solr
jdbc.dialect=org.hibernate.dialect.HSQLDialect
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:myapp
jdbc.username=sa
jdbc.password=

此配置非常直接且有效 - 无视图。当 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter 发挥作用时,会出现问题。

问题描述

OpenSessionInViewFilter 确保在视图试图显示这些对象的内容时,可以延迟加载对象图中未在打开的事务中加载的实例(如果视图试图显示这些对象的内容)(参见{{3 }})。正如所描述的那样,此过滤器在delpoyment描述符 web.xml 中声明(参见[1]):

<filter>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

如果 myapp-persistence-context.xml 包含在上面的 myapp-servlet.xml 中,那么 context:property-placeholder 可以正常工作, OpenSessionInViewFilter 找不到必要的 sessionFactory 。原因似乎是Spring首先处理 web.xml 然后 myapp-servlet.xml ,它导入 myapp-persistence-context.xml 。不幸的是,我无法用引用证明这个猜测。抛出以下异常:

GRAVE: Servlet.service() for servlet [myapp] in context with path [/myapp] threw exception
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'sessionFactory' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1097)
    at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:242)
    at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:227)
    at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:171)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)

部署描述符通常包含不同的应用程序上下文部分,其中包含 ContextLoaderListener ,而不是 myapp-servlet.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:myapp-service-context.xml, 
        classpath*:myapp-persistence-context.xml
    </param-value>
</context-param>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

有了这种配置,不幸的是,Spring的上下文:property-placeholder 机制似乎不再起作用了。

目标和问题

myapp-persistence (。jar)和 myapp-service (.jar)等模块必须在运行时通过引用上下文与属性文件配置,例如 myapp-web (.war)的应用程序上下文。

问题是:是否可以在Spring应用程序上下文中配置 OpenSessionInViewFilter ,以便 context:property-placeholder 仍然可用?

或者替代方案:如果应用程序上下文部分包含在部署描述符 web.xml 中,Spring如何在运行时初始化应用程序上下文中的变量?

基本上:为什么实际上需要配置 OpenSessionInViewFilter ,为什么Spring MVC不能透明地支持开箱即用的视图延迟加载?

预期备注

编译时的属性替换不是重点。已使用Maven Filtering创建配置文件相关属性文件。

dataSource solrServer 声明移植到 myapp-servlet.xml 中(参见[2],{{3} })不是一个可接受的解决方案,因为它破坏了 myapp-persistence (。jar)和 myapp-service (。jar)的模块性和独立可测试性 - 实际上是精神依赖注入!

2 个答案:

答案 0 :(得分:3)

与同事交谈带来了解决方案:如果我使用Spring的拦截器而不是Servlet-API的过滤机制,context:property-placeholder仍然可用。我删除了contextConfigLocation myapp-persistence-context.xml 的引用和 web.xml 中的OpenSessionInViewFilter,并声明了{{1} } myapp-persistence-context.xml

OpenSessionInViewInterceptor

myapp-persistence (。jar)的类路径中的 myapp-persistence-context.xml myapp-servlet.xml中的import语句< / em>如上所述,Spring在运行时使用 myapp-configuration.properties 中的值替换所有属性变量。模块化保存最佳! Will Keelingexternalization of the property file完成了项目设置。

另请参阅讨论Spring HandlerInterceptor vs Servlet FiltersSpring doc

答案 1 :(得分:-1)

您当然应该将sessionFactory和非与Web相关的bean保留在contextConfigLocationweb.xml定义的根应用程序上下文中。除了模块化方面(正如您所提到的),OpenSessionInViewFilter需要这种方式,因为它在根Web应用程序上下文中查找sessionFactory并且it will error if it can't find it there - 正如您所发现的那样。因此,contextConfigLocation设置是正确的方法。

PropertyPlaceholderConfigurerBeanFactoryPostProcessor,这意味着它在定义它的bean工厂的上下文中工作。在这种情况下,它在 myapp-servlet.xml 中定义,这意味着它将在Web上下文中工作,但它不会解析根应用程序上下文中的占位符(dataSource并且定义了solrServer

我的建议是将<context:property-placeholder>从网络移动到根应用程序上下文 - 但参数化location允许由封闭的应用程序设置。例如,您可以将其添加到 myapp-service-context.xml

<context:property-placeholder location="${props.file}"/>

然后您可以将其留给 myapp-web.war (或者无论父应用程序是什么)来设置文件的位置。例如,这可以作为系统属性完成:

-Dprops.file=file:C:/myapp-configuration.properties