javaspringspring-mvcspring-java-config

Spring Java Config with Multiple Dispatchers


I've some experience Spring now and also have some pure java config web-apps in use. However, these are usually based on a quiet simple setup:

For my current project I need to have separate dispatcher contexts with different configuration. That's not a problem with the XML based configuration as we have a dedicated ContextLoaderListener that's independent from Dispatcher Configuration. But with java config I'm not sure if what I'm doing is fine so far ;)

Here's a common DispatcherConfig:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new class[]{MyAppConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[]{MyDispatcherConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
    return new String[]{"/mymapping/*"};
  }

  @Override
  protected String getServletName() {
    return "myservlet";
  }
}

As said, I need a second (third, ...) dispatcher with another mapping (and view resolvers). So, I copied the config and added for both getServletName() (otherwise both will be named as 'dispatcher' which will cause errors). The second config was looking like that:

public class AnotherWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new class[]{MyAppConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[]{AnotherDispatcherConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
    return new String[]{"/another_mapping/*"};
  }

  @Override
  protected String getServletName() {
    return "anotherservlet";
  }
}

When I use it like this, starting application results in a problem with ContextLoaderListener:

java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:277)
...

So I removed the second MyAppConfig.class return from one of the AbstractAnnotationConfigDispatcherServletInitializer and it works fine. However, that doesn't feel to be the right way ;)

For my understanding: should all DispatcherConfig be handled within one AbstractAnnotationConfigDispatcherServletInitializer or should I separate them as I did? I tried to configure them in one class but then my config was totally mixed (so I believe that's not the desired way).

How do you implement such a case? Is it possible to set the ContextLoaderListener in java config outside of the AbstractAnnotationConfigDispatcherServletInitializer? Or should I create a DefaultServlet which has only the root config? What about implementing the base interface of that configuration WebApplicationInitializer?


Solution

  • Mahesh C. showed the right path, but his implementation is too limited. He is right on one point : you cannot use directly AbstractAnnotationConfigDispatcherServletInitializer for multiple dispatcher servlet. But the implementation should :

    Here is a more complete implementation :

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // root context
        AnnotationConfigWebApplicationContext rootContext =
                new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class); // configuration class for root context
        rootContext.scan("...service", "...dao"); // scan only some packages
        servletContext.addListener(new ContextLoaderListener(rootContext));
    
        // dispatcher servlet 1
        AnnotationConfigWebApplicationContext webContext1 = 
                new AnnotationConfigWebApplicationContext();
        webContext1.setParent(rootContext);
        webContext1.register(WebConfig1.class); // configuration class for servlet 1
        webContext1.scan("...web1");            // scan some other packages
        ServletRegistration.Dynamic dispatcher1 =
        servletContext.addServlet("dispatcher1", new DispatcherServlet(webContext1));
        dispatcher1.setLoadOnStartup(1);
        dispatcher1.addMapping("/subcontext1");
    
        // dispatcher servlet 2
        ...
    }
    

    That way, you have full control on which beans will end in which context, exactly as you would have with XML configuration.