Domanda

Initial situation

My web application consists of the Maven modules myapp-persistence(.jar), myapp-model(.jar), myapp-service(.jar) and myapp-web(.war) to get a conventional, loosely coupled, multi-tiered architecture. All modules are joined together by a parent Maven module, which only contains the parent POM with general definitions for all sub modules.

Especially myapp-service(.jar) and myapp-persistence(.jar) hold their own configurable (!) application context parts with the needed objects. Both jars must be deployable with the containing variable definitions, in other words the jars must not have concrete values for the variables.

myapp-service-context.xml declares a solrServer bean with the variable of the server URL:

<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 defines a dataSource with connection variables:

<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) references myapp-service(.jar) and myapp-persistence(.jar). In myapp-servlet.xml it includes their application context parts and provides the property values for configuration of the declared beans by a property file. By context:property-placeholder Spring initializes all the variables with the concrete values when it creates the application context in memory.

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

For the development profile the concrete myapp-configuration.properties may look like:

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=

This configuration is imo straight forward and works - without view. The problem arises when org.springframework.orm.hibernate3.support.OpenSessionInViewFilter comes into play.

Problem description

OpenSessionInViewFilter ensures that instances in the object graph which are not loaded within an open transaction during the controller processing can be lazily loaded, if the view tries to display these objects´ content (see [1]). As often described this filter is declared in the delpoyment descriptor web.xml (see [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>

If myapp-persistence-context.xml is included in myapp-servlet.xml like above so that context:property-placeholder works, OpenSessionInViewFilter does not find the necessary sessionFactory. The reason seems to be that Spring first processes web.xml and then myapp-servlet.xml, which imports myapp-persistence-context.xml. Unfourtunately I can´t proof this guess by a reference. Following exception is thrown:

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)

Different application context parts are usually included by the deployment descriptor with a ContextLoaderListener and not by 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>

With this configuration unfourtunately, Spring´ s context:property-placeholder mechanism seems not to work any more.

Objective and Question

Modules like myapp-persistence(.jar) and myapp-service(.jar) must be configurable at run-time with a property file by the referencing context, e.g. the application context of myapp-web(.war).

The question is: Is it possible to configure OpenSessionInViewFilter in Spring application context so that context:property-placeholder is still useable?

Or alternatively: How can variables in applicaton contexts be initialized by Spring at run-time, if application context parts are included in the deployment descriptor web.xml?

Fundamentally: Why is actually OpenSessionInViewFilter necessary to be configured, why Spring MVC does not transparently support view lazy loading out-of-the-box?

Anticipating Remarks

Property replacement at compile-time is not the point here. The profile dependent property file is already created with Maven Filtering.

Moving dataSource and solrServer declarations into myapp-servlet.xml as already proposed (see [3], [4]) is not an acceptable solution, because it destroyes modularity and independent testability of myapp-persistence(.jar) and myapp-service(.jar) - actually the spirit of dependency injection!

È stato utile?

Soluzione

Talking to a colleague brought the solution: context:property-placeholder remains useable if I use Spring´s interceptor instead of the Servlet-API´s filter mechanism. I removed the reference of myapp-persistence-context.xml in contextConfigLocation and the OpenSessionInViewFilter from web.xml and declared an OpenSessionInViewInterceptor in myapp-persistence-context.xml:

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

With myapp-persistence-context.xml in the classpath of myapp-persistence(.jar) and the import statement in myapp-servlet.xml as described above, Spring replaces all property variables with the values in myapp-configuration.properties at run-time as intended. Modularity is saved at its best! Will Keeling´s externalization of the property file completes the project setup.

See also the discussion Spring HandlerInterceptor vs Servlet Filters and the Spring doc.

Altri suggerimenti

You should certainly keep the sessionFactory and non-web related beans in the root application context as defined by the contextConfigLocation in the web.xml. Apart from the modularity aspect (as you mention), the OpenSessionInViewFilter needs it this way because it looks for the sessionFactory in the root web application context and it will error if it can't find it there - as you discovered. So your contextConfigLocation setup is the correct way to go.

PropertyPlaceholderConfigurer is a BeanFactoryPostProcessor which means it works within the context of the bean factory in which it is defined. In this case, it is defined in myapp-servlet.xml which means it will work within the web context but it won't resolve the placeholders in the root application context (where the dataSource and solrServer are defined).

My suggestion would be to move the <context:property-placeholder> from the web to the root application context - but parameterize the location allowing this to be set by the enclosing app. For example, you could add this to your myapp-service-context.xml

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

And then you can leave it to the myapp-web.war (or whatever the parent app happens to be) to set the location of the file. For example, this could be done as a system property:

-Dprops.file=file:C:/myapp-configuration.properties
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top