Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • application config for services / repositories
  • dispatcher config for one dispatcher (and some controllers)
  • (optional) spring security to secure the access

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?

like image 780
delimiter Avatar asked Mar 05 '15 12:03

delimiter


2 Answers

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 :

  • create a root application context
  • gives it an initial configuration and say what packages it should scan
  • add a ContextListener for it to the servlet context
  • then for each dispatcher servlet
    • create a child application context
    • gives it the same an initial configuration and packages to scan
    • create a DispatcherServlet using the context
    • add it to the servlet context

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.

like image 145
Serge Ballesta Avatar answered Oct 29 '22 03:10

Serge Ballesta


I think you can work it out if you use generic WebApplicationInitializer interface rather than using abstract implementation provided by spring - AbstractAnnotationConfigDispatcherServletInitializer.

That way, you could create two separate initializers, so you would get different ServletContext on startUp() method and register different AppConfig & dispatcher servlets for each of them.

One of such implementing class may look like this:

public class FirstAppInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext container) throws ServletException {

        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(AppConfig.class);
        ctx.setServletContext(container);

        ServletRegistration.Dynamic servlet = container.addServlet(
                "dispatcher", new DispatcherServlet(ctx));

        servlet.setLoadOnStartup(1);
        servlet.addMapping("/control");

    }

}
like image 26
Mahesh C. Avatar answered Oct 29 '22 04:10

Mahesh C.