初始情况
我的 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 过滤创建了配置文件相关属性文件。
将dataSource和solrServer声明移动到已经提出的 myapp-servlet.xml中(参见[3],[4])不是一个可接受的解决方案,因为它破坏了myapp-persistence (.jar) 和myapp-service的模块化和独立可测试性( .jar) - 实际上是依赖注入的精神!