3

初始情况

我的 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定义了一个带有连接变量的数据源:

<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=

这种配置是 imo 直截了当的并且可以工作 - 没有视图。当org.springframework.orm.hibernate3.support.OpenSessionInViewFilter发挥作用时,问题就出现了。

问题描述

OpenSessionInViewFilter确保在控制器处理期间未在打开的事务中加载的对象图中的实例可以延迟加载,如果视图试图显示这些对象的内容(参见[1])。正如经常描述的那样,这个过滤器是在 delpoyment 描述符web.xml中声明的(参见[2]):

<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。不幸的是,我无法通过参考来证明这个猜测。抛出以下异常:

坟墓:servlet [myapp] 在路径 [/myapp] 的上下文中的 Servlet.service() 引发异常
org.springframework.beans.factory.NoSuchBeanDefinitionException:没有定义名为“sessionFactory”的bean
    在 org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529)
    在 org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
    在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277)
    在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    在 org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1097)
    在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:242)
    在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:227)
    在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:171)
    在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    在 org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    在 org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    在 org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    在 org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    在 org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    在 org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    在 java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    在 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 的context:property-placeholder机制似乎不再起作用了。

目标和问题

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

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

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

从根本上说:为什么实际上必须配置OpenSessionInViewFilter ,为什么 Spring MVC 不透明地支持开箱即用的视图延迟加载?

预期的评论

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

dataSourcesolrServer声明移动到已经提出的 myapp-servlet.xml中(参见[3][4])不是一个可接受的解决方案,因为它破坏了myapp-persistence (.jar) 和myapp-service的模块化和独立可测试性( .jar) - 实际上是依赖注入的精神!

4

2 回答 2

3

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

<mvc:interceptors>
    <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</mvc:interceptors>

如上所述,使用myapp-persistence (.jar)的类路径中的myapp -persistence-context.xml和myapp-servlet.xml中的 import 语句,Spring在运行时将所有属性变量替换为myapp-configuration.properties中的值-按预期时间。模块化被保存在最好的状态!Will Keeling对属性文件的外部化完成了项目设置。

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

于 2014-02-12T16:56:22.817 回答
-1

您当然应该将sessionFactorybean 和非 Web 相关 bean 保留在根应用程序上下文中,contextConfigLocationweb.xml. 除了模块化方面(正如您所提到的)之外,OpenSessionInViewFilter它还需要这种方式,因为它会sessionFactory在根 Web 应用程序上下文中查找,如果在那里找不到它会出错- 正如您所发现的那样。所以你的contextConfigLocation设置是正确的方法。

PropertyPlaceholderConfigurer是 aBeanFactoryPostProcessor这意味着它在定义它的 bean 工厂的上下文中工作。在这种情况下,它是在 myapp-servlet.xml中定义的,这意味着它将在 Web 上下文中工作,但它不会解析根应用程序上下文中的占位符(定义dataSourceand的地方)。solrServer

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

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

然后你可以把它留给myapp-web.war(或任何父应用程序)来设置文件的位置。例如,这可以作为系统属性来完成:

-Dprops.file=file:C:/myapp-configuration.properties
于 2014-01-29T22:24:42.293 回答