0

I'm having a spring mvc application with two contexts (as declared in my AbstractAnnotationConfigDispatcherServletInitializer subclass)

  • the root context contains models, repositories, etc
  • the mvc context contains the controllers

Now, I can inject a repository into a controller, but why?

  • Does the web context also include the beans from the root context (something like @Import??). The documentation implies they have a parent-child relationship, but by inspecting the web context I don't the repository beans inside it.
  • Or, does @Autowired work across multiple contexts? And, if so, how??
blue_note
  • 27,712
  • 9
  • 72
  • 90

2 Answers2

1

Both contexts are stored in the same servlet context.

If you notice, AbstractDispatcherServletInitializer,the parent class of AbstractAnnotationConfigDispatcherServletInitializer, does the registration on the onStartup method

@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        registerDispatcherServlet(servletContext);
    }

To do that, first calls its parent onStartup method where it first adds the rootApplicationContext which is created by the createRootApplicationContext method of the AbstractAnnotationConfigDispatcherServletInitializer class and finally adds it to the ServletContext received on the onStartup method:

@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerContextLoaderListener(servletContext);
    }

    /**
     * Register a {@link ContextLoaderListener} against the given servlet context. The
     * {@code ContextLoaderListener} is initialized with the application context returned
     * from the {@link #createRootApplicationContext()} template method.
     * @param servletContext the servlet context to register the listener against
     */
    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            servletContext.addListener(listener);
        }
        else {
            logger.debug("No ContextLoaderListener registered, as " +
                    "createRootApplicationContext() did not return an application context");
        }
    }

And after that, AbstractDispatcherServletInitializer calls its registerDispatcherServlet method where it calls the abstract method createServletApplicationContext that is in the AbstractAnnotationConfigDispatcherServletInitializerclass and creates the dispatcherServlet that is then added to the same ServletContext:

    /**
     * Register a {@link DispatcherServlet} against the given servlet context.
     * <p>This method will create a {@code DispatcherServlet} with the name returned by
     * {@link #getServletName()}, initializing it with the application context returned
     * from {@link #createServletApplicationContext()}, and mapping it to the patterns
     * returned from {@link #getServletMappings()}.
     * <p>Further customization can be achieved by overriding {@link
     * #customizeRegistration(ServletRegistration.Dynamic)} or
     * {@link #createDispatcherServlet(WebApplicationContext)}.
     * @param servletContext the context to register the servlet against
     */
    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return empty or null");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext,
                "createServletApplicationContext() did not return an application " +
                "context for servlet [" + servletName + "]");

        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        Assert.notNull(registration,
                "Failed to register servlet with name '" + servletName + "'." +
                "Check if there is another servlet registered under the same name.");

        registration.setLoadOnStartup(1);
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());

        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);
    }

That's why you can inject a repository in a controller, because the 2 contexts are in the same ServletContext.

mlg
  • 5,248
  • 1
  • 14
  • 17
0

I typically return a single context from AbstractAnnotationConfigDispatcherServletInitializer.getRootConfigClasses() this class then has ComponentScan and/or Imports which finds @Configurations and @Components etc.

Unless you explicitly create parent-child contexts, all your beans, components, services ends up in a single ApplicationContext. By explicit I mean that you have to call setParent() on ApplicationContext before refresh() is called, so you typically know when you have done it.

This means that you can @AutoWire into an other spring bean from the same application context (the autowired bean may be from a parent context if you have nested contexts)

Edit If you use AbstractAnnotationConfigDispatcherServletInitializer.getServletConfigClasses() and return two contexts from you initializer, the servletConfig context will be a child and the root context will be the parent. This means that you can autowire beans from the RootConfig context into the servletConfig context beans, but not the other way around. - This is why I typically only return a context from getRootConfigClasses() and null from getServletConfigClasses().

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • typically, the same class also defines a dispatcher servlet context, and the documentation explicitly states that usually there are two contexts in spring mvc. the `.setParent` might have been called internally for these two beans – blue_note Jan 09 '17 at 15:16
  • Personally I only use parent child contexts if there lifecycle is different, and the parent can live without the child - This is not the case in a web app, so I only return a class from getRootConfigClasses(). – Klaus Groenbaek Jan 09 '17 at 15:25