【问题标题】:spring: where does `@autowired` look for beans?spring: `@autowired` 在哪里寻找 bean?
【发布时间】:2017-05-23 21:04:27
【问题描述】:

我有一个带有两个上下文的 spring mvc 应用程序(在我的AbstractAnnotationConfigDispatcherServletInitializer 子类中声明)

  • 根上下文包含模型、存储库等
  • mvc 上下文包含控制器

现在,我可以将存储库注入控制器,但为什么呢?

  • Web 上下文是否还包括来自根上下文的 bean(类似于 @Import??)。该文档暗示它们具有父子关系,但是通过检查 Web 上下文,我没有在其中存储存储库 bean。
  • 或者,@Autowired 是否可以跨多个上下文工作?如果是的话,怎么做??

【问题讨论】:

  • 你能发布你的 web.xml 文件吗?
  • 我没有web.xml,通过提到的子类进行初始化

标签: spring spring-mvc dependency-injection


【解决方案1】:

两个上下文都存储在同一个 servlet 上下文中。

如果你注意到,AbstractDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializer 的父类,在 onStartup 方法上进行注册

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

为此,首先调用其父 onStartup 方法,其中首先添加由 AbstractAnnotationConfigDispatcherServletInitializer 类的 createRootApplicationContext 方法创建的 rootApplicationContext,最后将其添加到 onStartup 方法接收到的 ServletContext 中:

@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");
        }
    }

然后,AbstractDispatcherServletInitializer 调用它的registerDispatcherServlet 方法,它调用AbstractAnnotationConfigDispatcherServletInitializer 类中的抽象方法createServletApplicationContext 并创建dispatcherServlet,然后将其添加到同一个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);
    }

这就是您可以在控制器中注入存储库的原因,因为这两个上下文位于同一个 ServletContext 中。

【讨论】:

    【解决方案2】:

    我通常从 AbstractAnnotationConfigDispatcherServletInitializer.getRootConfigClasses() 这个类返回单个上下文,然后有 ComponentScan 和/或 Imports 找到 @Configurations 和 @Components 等。

    除非您显式创建父子上下文,否则您的所有 bean、组件、服务最终都在一个 ApplicationContext 中。明确地说,我的意思是您必须在调用 refresh() 之前在 ApplicationContext 上调用 setParent(),因此您通常知道何时完成。

    这意味着您可以从同一应用程序上下文 @AutoWire 到另一个 spring bean(如果您有嵌套上下文,自动装配的 bean 可能来自父上下文)

    编辑 如果您使用 AbstractAnnotationConfigDispatcherServletInitializer.getServletConfigClasses() 并从初始化程序返回两个上下文,则 servletConfig 上下文将是子上下文,而根上下文将是父上下文。这意味着您可以将 RootConfig 上下文中的 bean 自动装配到 servletConfig 上下文 bean 中,但反之则不行。 - 这就是为什么我通常只从getRootConfigClasses()nullgetServletConfigClasses() 返回一个上下文。

    【讨论】:

    • 通常,同一个类还定义了一个dispatcher servlet context,文档明确说明spring mvc中通常有两个context。 .setParent 可能已在内部为这两个 bean 调用
    • 就我个人而言,如果生命周期不同,我只使用父子上下文,并且父子可以在没有孩子的情况下生活 - 这在 Web 应用程序中不是这种情况,所以我只从 getRootConfigClasses() 返回一个类.
    猜你喜欢
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-02
    • 2012-05-21
    • 2011-08-01
    • 2015-07-28
    相关资源
    最近更新 更多