Announcement Announcement Module
Collapse
No announcement yet.
Dynamically loading more beans in an XmlWebApplicationContext Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Dynamically loading more beans in an XmlWebApplicationContext

    I'd like to make a bean, let's call it a DynamicContextReader, that would use some non-spring config information to determine the names of additional spring XML files to load and then load then dynamically INTO THE SAME WEB APPLICATION CONTEXT it's being loaded from.

    I'm having a little difficulty doing this. I tried invoking loadBeanDefinitions() and refresh() from within my DynamicContextReader's init method, but ran into a ConcurrentModificationException, presumably because i was trying to load bean defs while I was already loading bean defs

    I then tried to load these XML files into their own individual GenericWebApplicationContext, but then my DispatcherServlet bean (instantiated in one of these dynamically determined files) failed to initialize because it insists on being run in the original WebApplicationContext, not the GenericWebApplicationContext I had just created:

    Code:
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'collectorServlet' defined in class path resource [collector-context.xml]: Initialization of bean failed; nested exception is org.springframework.context.ApplicationContextException: Cannot reinitialize with different application context: current one is [org.springframework.web.context.support.XmlWebApplicationContext: display name [Root WebApplicationContext]; startup date [Fri Jan 12 16:09:25 PST 2007]; root of context hierarchy; config locations [/WEB-INF/applicationContext.xml]], passed-in one is [org.springframework.web.context.support.GenericWebApplicationContext: display name [org.springframework.web.context.support.GenericWebApplicationContext;hashCode=14794804]; startup date [Fri Jan 12 16:09:25 PST 2007]; root of context hierarchy]
    Here's the (simplified) code I used to do this from within my little DynamicContextReader bean:

    Code:
        public void init() {
            logger.info("Loading bean definitions from " + resource );
            GenericWebApplicationContext factory = new GenericWebApplicationContext( (DefaultListableBeanFactory) applicationContext.getBeanFactory() );
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader( factory );
            reader.loadBeanDefinitions(resource);
            factory.refresh();
        }
    But of course it doesn't work. Is it possible to use a spring bean to dynamically load additional xml files into an existing XmlWebApplicationContext?

    Thanks
    Mike

  • #2
    It depends what you mean by "dynamically". You might mean "at start up, when the main application context is being located / created", or you might mean "at any random time you feel like it during a running application". In the former case the conventional tool is SingletonBeanFactoryLocator (or the appplication context version of the same thing). It is usually useful to leverage the parent-child structure of an application context - and I think if you got your head round that you would find that the errors you are seeing will go away. In the latter case you really have to be very careful what beans are in the dynamic context (e.g. you can't expect to be able to simply throw away and re-initialise database or persistence resources). Spring OSGi is designed to deal correctly with this scenario, but it hasn't been released yet.

    Comment


    • #3
      I've written something called DynamicContextLoaderListener that essentially does this. It's a bit more elaborate, keying off a bean context ID for the parent context. That key is configurable at runtime (i.e. not hard-coded into the web.xml file).

      This may not be exactly what you are looking for, but it also has a hook for loading a series of XML files into the WebApplicationContext itself... one for each context ID. This is all configured in a properties file... the name of the context ID is the name, and a comma-separated list is the value.

      Comment


      • #4
        Thank you for the DynamicContextLoaderListener idea, fiddlerpianist. I looked it up on another thread you posted in the forum (http://forum.springframework.org/showthread.php?t=33105) and i think it's not quite what i was looking for.

        I was hoping to be able to dynamically load additional XML context files from within a bean rather than by modifying the ContextLoader directly. A philosophical difference perhaps, but so far I've found it easier to explain to my coworkers how beans work than I've found it to explain the nuances of the ApplicationContext. I've been progressively stripping out my modifications to the ContextLoader for this reason.

        Anyway, i think i found the right solution. The proper way to do this seems to be to create a bean that implements BeanFactoryPostProcessor and calls XmlBeanDefinitionReader.loadBeanDefinitions(factor y) then.

        For the benefit of posterity, I also went down a couple of wrong paths as well. Trying to modify the BeanFactory from within the init() method of a bean doesn't work, you get a ConcurrentModificationException IIRC. Similarly, trying to modify the factory by using a MessageListener bean listening for the ContextRefreshedEvent doesn't work either -- it worked for one of my apps but not for another, the exact reason I'm not fully clear on yet.

        Anyway, BeanFactoryPostProcessor. Good stuff. Thanks everyone for your comments.

        Comment


        • #5
          Anyway, i think i found the right solution. The proper way to do this seems to be to create a bean that implements BeanFactoryPostProcessor and calls XmlBeanDefinitionReader.loadBeanDefinitions(factor y) then.
          I've been struggling with this problem for a few days now. I also want to load new XML context files after application startup.

          I can figure out how to load the xml file, create a bean factory out of it, but how can I find what beans have already been loaded in the servlet and add new ones?

          Comment


          • #6
            Hm, not sure I understand your question nbilyk. For my purposes it didn't matter to me what had previously been loaded, i could just load the new files willy-nilly once I determined which files to load.

            So you need to not only dynamically determine which context files to load, but you also need to dynamically determine which beans in those files to load? Sounds like you'll need a custom solution.

            Comment


            • #7
              as I've been messing with this more, I think I can better describe my situation.

              I have plugins that contain new applicationContext-*.xml files.
              They also have xml files with struts action definitions.
              The action definitions need to see the beans that the applicationContext-*.xml files define.

              So I think I'm getting closer now that I understand that it's Struts that needs to be aware of these new beans.

              Comment


              • #8
                Ok, I figured out my problem. I couldn't get the application to see beans or beanfactories I was creating.
                The key is retrieving THE ApplicationContext.

                WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContex t(ServletActionContext.getServletContext());
                <-- Add your new beans to the wac object.
                wac.publishEvent(new ContextRefreshedEvent(wac));

                I saw a lot of posts about loading applicationContext.xml files after application startup, but they all led me to dead ends. Hope this helps someone.

                Comment


                • #9
                  nbilyk, can you please explain what you meant by "Add your new beans to the wac object". From what I can see there are no methods on WebApplicationContext to do that.

                  Thanks.

                  Originally posted by nbilyk View Post
                  Ok, I figured out my problem. I couldn't get the application to see beans or beanfactories I was creating.
                  The key is retrieving THE ApplicationContext.

                  WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContex t(ServletActionContext.getServletContext());
                  <-- Add your new beans to the wac object.
                  wac.publishEvent(new ContextRefreshedEvent(wac));

                  I saw a lot of posts about loading applicationContext.xml files after application startup, but they all led me to dead ends. Hope this helps someone.

                  Comment


                  • #10
                    I'm not at work right now so I don't have the exact syntax in front of me, but when I say add new beans to the wac object, you can get a DefaultListableBeanFactory object from the wac object. It's something like getBeanFactory or something like that.
                    Then from the DefaultListableBeanFactory object you can call registerBean()

                    again, I don't have the exact syntax in front of me, but that's the idea.

                    Hope it helps,

                    -Nick

                    Comment


                    • #11
                      Wow. Thanks Nick for such a quick reply.

                      I've found the method calls you were talking about - DefaultListableBeanFactory.registerBeanDefinition( ) - and have got it to work. One thing I note is that if you do:

                      Code:
                      wac.refresh();
                      instead of

                      Code:
                      wac.publishEvent(new ContextRefreshedEvent(wac));
                      you lose the registered bean, so you can only really use this technique as a runtime thing or maybe perhaps as part of some sort of application bootstrapping.

                      Anyway, thanks again.

                      Comment


                      • #12
                        you lose the registered bean, so you can only really use this technique as a runtime thing or maybe perhaps as part of some sort of application bootstrapping.
                        I'm not 100% I follow what you're saying. If you mean that you're losing the beans that were previously registered -- when you create a new bean factory, try setting the existing one as parent.
                        This is what I'm doing, at runtime, I'm loading jars that contain applicationContext.xml files. I'm able to get the currently registered beans from the application context, set that beanfactory as the parent of my new one, and all new and old beans now are registered.

                        I will try the wac.refresh() idea, thanks.

                        Comment

                        Working...
                        X