Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC: Sharing context within ear

I have an ear package that contains one jar with common objects and two war webapps that I'd like to use the common jar. I've setup the configuration to use application wide context via ContextLoaderListener and webapp contexts separately for DispatcherServlet.

The setup of my demo app is roughly the following

  • common.jar contains applicationContext.xml and beanRefContext.xml, which are supposed to be application (ear) wide context. The files are like below. shared namespace is where the shared bean is located.

applicationContext

<beans>
    <!-- namespace etc declarations omitted -->
    <context:annotation-config />
    <context:component-scan base-package="study.spring.multicontext.shared" />
</beans>

beanRefContext.xml

<beans>
    <!-- namespace etc declarations omitted -->
<bean id="sharedContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
        <list>
            <value>classpath*:applicationContext.xml</value>
        </list>
    </constructor-arg>
</bean>
</beans>
  • webapp1 and webapp2 are Spring MVC applications packaged as separate wars with web.xml file like below

    <web-app>
    
    <context-param>
      <param-name>parentContextKey</param-name>
      <param-value>sharedContext</param-value>
    </context-param>
    <context-param>
      <param-name>locatorFactorySelector</param-name>
      <param-value>classpath:beanRefContext.xml</param-value>
    </context-param>
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
    
    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <servlet>
        <servlet-name>dos</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>/WEB-INF/dos-servlet.xml</param-value>
        </init-param>
    
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    
    <servlet-mapping>
        <servlet-name>dos</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

and xx-servlet.xml like for webapp specific context. web namespace is where the controllers are located.

<beans>
    <!-- namespace etc declarations omitted -->

    <context:component-scan base-package="study.spring.multicontext.web"/>
    <mvc:annotation-driven />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

      <property name="suffix" value=".jsp"/>
    </bean>

</beans>
  • The shared bean is @Autowired in normal fashion in Controller classes

    @Autowired
    MySharedBean mySharedBean
    
  • ear package contains both wars and jar, and structure is like

    ear
     |
     |--common.jar
     |   |--META-INF
     |   |--applicationContext.xml
     |   |--beanRefContext.xml
     |
     |--webapp1.war
     |   |--WEB-INF
     |       |--xx-servlet.xml
     |       |--web.xml
     |
     |--webapp2.war
     |   |--WEB-INF
     |       |--xx-servlet.xml
     |       |--web.xml
    

The problem is that there will still be two instances of the bean. One for each controller/webapp, since there's only one Controller in each of the wars. I have tried to twiddle with the configuration, but no matter what I do, I either get zero instances or two instances.

I checked the references with Eclipse MAT from a memory dump, and there are actually 4 instances, but I guess the two are for Spring internal use. Anyway, from there it's clearly visible that each controller has it's own instance.

I've read numerous of blog posts, discussion forums, etc where they say that this should be as simple as this. Some suggest JNDI, but as I've understood, this should be possible without it.

And it's not possible to combine the wars and bundle the jar inside. As it might work for this demo app, the real life case I'm working with does not allow this.

Any help on this matter is highly appreciated. Thanks in advance.

SpringSource example from 2007 for Spring 2.X that does the same but with different configuration. A bit outdated and looking for a Spring 3.X based solution, as dscribed in the bounty description.

like image 659
kaskelotti Avatar asked Apr 23 '13 06:04

kaskelotti


4 Answers

I do not believe anything has changed from Spring 2.x to 3.x as far as application context hierarchies are concerned.

From what I can tell, the issue with your config is that you are are loading the applicationContext.xml - the one which is loaded into the sharedContext, is also being loaded by each webapp, because of the fact that its mentioned in the context-param contextConfigLocation.

Since the same file is loaded twice, once in the parent context and once in the web application's root context, there are copies made, and the child context, ie. webapp, uses the ones it created, not the ones that are present in the parent.

Change your config so you don't reload the same beans xml twice, and it should work fine. You can use parentContextKey and contextConfigLocation both just don't load the same files.

Update: In addition to the above, you also need to check if the shared jar is visible to the wars (visible as in allowed to share the same instance.). I tried to run the sample from the blog and it did not work for me when I deployed it as a Java EE6 application, and that's because the rules for ear jar visibility inside wars changed from Java EE5 to EE6. When I run the sample in compatibility mode of Glass Fish, everything works as expected.

So check your EAR / WARs to see what servlet spec you are running, and make sure your server is deploying the application accordingly.

If you have to upgrade to Java EE 6, make sure you are following the latest visibility rules http://docs.oracle.com/cd/E19226-01/820-7688/gjjdt/index.html. Check the MANIFEST files of the wars to ensure they have all ear jars explicitly mentioned in the Class-Path configuration.

Hope this helps.

like image 118
Akshay Avatar answered Oct 22 '22 02:10

Akshay


I got it solved.

The problem was in class loading as I suspected in comments to @Akshay's answer.

Maven included spring libs inside each war package, so they were loaded multiple times. To fix this, one needs to generate skinny wars.

I assume Akshay's note on his answer to remove the contextConfigLocation from context-params in web.xml was in key role as well.

like image 35
kaskelotti Avatar answered Oct 22 '22 02:10

kaskelotti


Though this question is old one, if someone is wondering why the documented approach is not working in Spring Framework 5.0+. The support for sharing context within ear is currently broken in Spring Framework 5.0+ as of posting of this answer. There is an existing issue open on Spring Framework Issue-20805

like image 3
anoviceuser Avatar answered Oct 22 '22 02:10

anoviceuser


We had a similar problem. Check this simple maven example (EAR with 2 WEB modules and a shared via parent spring context service module ) we have created for the experiment: EAR with shared spring context between wars

like image 2
Balaban Mario Avatar answered Oct 22 '22 02:10

Balaban Mario