Search code examples
javaspringservletsinitializationjavabeans

Spring add some beans to service context in Runtime


I am creating spring dispatcherServlet and set his servlet context this way:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;


import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.*;

public class MyDispatcherBean implements InitializingBean {


    @Autowired
    private ApplicationContext applicationContext;

    private DispatcherServlet dispatcherServlet;

    private final String someContext = "some.xml";

    private ServletContext servletContext;


    public void execute(/* Do some code..*/) throws IOException {

        /*
        Do some code..
        */
        try {
            dispatcherServlet.service(/* ...*/);
        } catch (ServletException e) {

        }

    }

    public WebApplicationContext getServiceContext() {
        return dispatcherServlet.getWebApplicationContext();
    }
    public void afterPropertiesSet() throws Exception {

        dispatcherServlet = new DispatcherServlet() {

            private static final long serialVersionUID = -7492692694742330997L;

            @Override
            protected WebApplicationContext initWebApplicationContext() {
                WebApplicationContext wac = createWebApplicationContext(applicationContext);
                if (wac == null) {
                    wac = super.initWebApplicationContext();
                }
                return wac;
            }

        };

        dispatcherServlet.setContextConfigLocation(someContext);
        dispatcherServlet.init(new DelegatingServletConfig());
    }

    private class DelegatingServletConfig implements ServletConfig {

        public String getServletName() {
            return "myDispatcher";
        }

        public ServletContext getServletContext() {
            return MyDispatcherBean.this.servletContext;
        }

        public String getInitParameter(String paramName) {
            return null;
        }

        public Enumeration<String> getInitParameterNames() {
            return Collections.enumeration(new HashSet<String>());
        }
    }

}

And i create that bean this way:

<bean id="myDispatcher" class="some.package.MyDispatcherBean " />

Then in any place in my code can exist any numbers of beans with code that get "myDispatcher" bean and add some beans in Runtime to servlet context of DispatcherServlet:

public class ContextAdder implements InitializingBean {


    @Autowired
    private ApplicationContext applicationContext;


    public String classPathToContext;

    public void afterPropertiesSet() throws Exception {


        DispatcherServlet dispatcherServlet = applicationContext.getBean("myDispatcher",DispatcherServlet.class);
        XmlWebApplicationContext webApplicationContext = (XmlWebApplicationContext) dispatcherServlet.getWebApplicationContext();
        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader((DefaultListableBeanFactory) webApplicationContext.getBeanFactory());
        xmlReader.loadBeanDefinitions(new ClassPathResource(classPathToContext));

    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public String getClassPathToContext() {
        return classPathToContext;
    }

    public void setClassPathToContext(String classPathToContext) {
        this.classPathToContext = classPathToContext;
    }
}

These beans are created this way:

<bean id="adder1" class="stargate.sg_1.ContextAdder" depends-on="myDispatcher">
        <property name="classPathToContext" value="Optimus.xml" />
</bean>

<bean id="adder2" class="stargate.atlantida.ContextAdder" depends-on="myDispatcher">
        <property name="classPathToContext" value="StarShip.xml" />
</bean>

In finally I expect behaviour: each ContextAdder bean add new beans to servlet context of dispatcherServlet in myDispatcher bean.

But i am afraid of some moments:

  1. Is it only one decision of this problem?
  2. Is it the best one?
  3. Each time when i add new bean to servletContext of DispatcherServlet - will it invoke refresh of ServletContext or even ApplicationContext? If it is true - does it have influence on performance?
  4. Is all lifecycles is correct?

Solution

  • I believe there is a better approach, but will leave it to you to decide.

    With your approach (implements InitializingBean) you are invoking your bean creation code after the bean definitions phase has completed and after the beans are being constructed. Your bean definition phase is very simple (just create "myDispatcher").

    I would recommend that you create/load all your bean definitions during the bean definition phase. One way to achieve this is to instead hook into the BeanFactory post processor (implements BeanFactoryPostProcessor). At this phase Spring lets you modify existing bean definitions and more importantly for you add further bean definitions. Now when you leave this phase, all beans will then be created in one phase. The approach is very natural: bean definitions are created => beans are created and wired => Done.

    public class ContextAdder implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
            throws BeansException {
    
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)factory);
    
        // I)  LOAD BY PATTERN MATCHING
        //PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(factory.getBeanClassLoader());
        //for (Resource resource : resourceResolver.getResources("com/.../*.xml"))
        //reader.loadBeanDefinitions(resource);
    
        // II)  LOAD A SINGLE FILE AT A TIME
        reader.loadBeanDefinitions(new ClassPathResource("com/../Optimus.xml""));
        .....
    }
    

    Perhaps you can adopt this concept to your unique requirement.