spring: where does `@autowired` look for beans?

流过昼夜 提交于 2019-12-23 04:56:16

问题


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??

回答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.




回答2:


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().



来源:https://stackoverflow.com/questions/41550796/spring-where-does-autowired-look-for-beans

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!