13

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.

kaskelotti
  • 4,709
  • 9
  • 45
  • 72

4 Answers4

7

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.

Akshay
  • 3,158
  • 24
  • 28
  • Thanks for your reply @Akshay. If I understood correctly, your suggestion is to remove `contextConfigLocation` from the `web.xml` files. I gave that a try and the result is IOException. Based on the log, `ContextLoaderListener` attempts to load applicationContext.xml from /WEB-INF/. And I think this is the reason I've added the location as context-param in the first place. – kaskelotti Apr 26 '13 at 04:01
  • [This blog post](http://internna.blogspot.fr/2007/05/sharing-global-spring-application.html) actaully suggests adding an empty applicationContext.xml in /WEB-INF/. Well, I did that and the app boots up OK, but the log shows I still have two instances of the shared bean. In fact, it seems that the `sharedContext` is created twice. – kaskelotti Apr 26 '13 at 04:26
  • can you try with no contextconfiglocation and no lovcator factory selector as cobtext params? – Akshay Apr 26 '13 at 05:18
  • I removed both `contextConfigLocation` and `locatorFactorySelector`, but the functionality remains the same: Two `sharedContext` instances are created. – kaskelotti Apr 26 '13 at 07:16
  • wow.. that's a brain teaser! how about this.. could you try using the sample application from the spring source blog? I think all you would have to do is put in new spring jars. and the ear should work. At least that way we'll know that sharing a parent context is possible, and might help you resolve what the issue is with your specific configuration. I am going to try and find time to do the same.. but might not be able to get to this soon. – Akshay Apr 26 '13 at 07:24
  • The sample app won't deploy on my jBoss, which made me notice something in jBoss classloading that might cause trouble here. I'll get back on this matter later. – kaskelotti Apr 26 '13 at 07:42
  • here's a doozy.. i deployed the sample application, no change at all, and looks like its not working! I see different instances of the shared service class in the two web apps! – Akshay Apr 26 '13 at 08:24
  • I installed a fresh JBAS6.1 and the sample app deploys and works as expected, so the problem must be somewhere in my configs. – kaskelotti Apr 26 '13 at 09:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/28966/discussion-between-akshay-singhal-and-j-andy) – Akshay Apr 26 '13 at 09:32
  • Hi, can somebody tell me if this "shared parent context" approach can be used to share a singleton bean across multiple WAR deployments in a Tomcat container? (i.e. where the WARs are simply deployed and are not packaged as part of an EAR.) Thanks in advance, PM – Going Bananas Jan 07 '14 at 17:13
  • In tomcat if you have separate wars, there's really no way to share a singleton across them through spring. Even a JNDI object has to be bound to a specific webcontext. – Akshay Jan 07 '14 at 17:42
  • Do you have any idea how the shared context can be closed automatically, when the server goes down or the WARs are stopped? If it creates Threadpools etc, not closing it will create trouble. – Dr. Hans-Peter Störr Oct 01 '15 at 10:57
  • @hstoerr That seems like a good question on its own, if you can post it as a new thread and mention an example, it will help others. – Akshay Oct 01 '15 at 13:19
3

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.

kaskelotti
  • 4,709
  • 9
  • 45
  • 72
3

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

2

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

Balaban Mario
  • 441
  • 5
  • 9