父子容器概念

父子容器并非 Spring MVC 的专利,在普通的 Spring 环境下 Spring 就已经设计出具有层次结构的容器了,这种设计方式也并非 Spring 独创,其工作方式和 ClassLoader (类加载器)很相似,每个容器有一个自己的父容器,但是与 ClassLoader 不同的是,通过容器查找 bean 时是优先从子容器查找,如果找不到才会从父容器中查找。当应用中存在多个容器时,这种设计方式可以将公共的 bean 放到父容器中,如果父容器中的 bean 不适用,子容器还可以覆盖父容器中的 bean。

根容器(父容器)通常包含基础设施bean,例如需要在多个Servlet实例之间共享的数据存储库和业务服务。 这些bean被有效地继承,并且可以在特定于Servlet的子容器中被覆盖(即重新声明),该子容器通常包含给定Servlet的本地bean。 下图显示了这种关系:

SpringMVC源码:父子容器的创建

代码实现:

需要继承AbstractAnnotationConfigDispatcherServletInitializer类,并实现三个抽象方法:

public class QuickWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	@Override //spring配置文件
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[]{AppConfig.class};
	}

	@Override //springmvc的配置文件
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[]{MvcConfig.class};
	}

	@Override //servlet映射
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}

两个配置类:

@ComponentScan("com.wj.service")
@Configuration
public class AppConfig {
}

@Configuration
@ComponentScan("com.wj.controller")
public class MvcConfig {
}

项目结构:这里MyWebApplicationInitializer不用管。

SpringMVC源码:父子容器的创建

按照上述配置后,就创建了一个父子容器。

那么如何验证我们创建了父子容器,我们在controller注入applicationContext,然后看applicationContext的parent字段,如果创建成功,则parent字段中一定有父容器:

SpringMVC源码:父子容器的创建

创建父子容器原理

上面创建父子容器案例中,我们继承AbstractAnnotationConfigDispatcherServletInitializer类后,就自动创建了父子容器。

我们先来看类的继承关系:

SpringMVC源码:父子容器的创建

该类实现了WebApplicationInitializer,那么势必会调用onStartup方法(具体原理可看前文:https://www.cnblogs.com/wwjj4811/p/15673030.html)

先来看AbstractContextLoaderInitializer的onStartup方法:

这里调用registerContextLoaderListener方法,内部会增加一个ContextLoaderListener的监听器。

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		//注册ContextLoaderListener
		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
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
                         //添加ApplicationContextInitializer
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}

	//创建根容器代码在AbstractAnnotationConfigDispatcherServletInitializer中
	@Override
	@Nullable
	protected WebApplicationContext createRootApplicationContext() {
		//获取根配置类(留给子类实现)
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			//创建IOC容器
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			//注册配置类
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

ContextLoaderListener实际上是一个ServletContextListener

当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由 ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。

contextInitialized(ServletContextEvent sce) :当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。

contextDestroyed(ServletContextEvent sce) :当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。

再看ContextLoaderListener的contextInitialized方法,看看它在servlet启动时候做了什么事情?

	@Override
	public void contextInitialized(ServletContextEvent event) {
		//初始化根容器
		initWebApplicationContext(event.getServletContext());
	}

initWebApplicationContext方法如下:这里是初始化父容器

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
                                        //这里cwac.getParent() 默认为null
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
                                                //loadParentContext()方法默认返回的也是null
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                                        //配置和刷新web容器,这里不再介绍,详情可看https://www.cnblogs.com/wwjj4811/p/15673030.html
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
                        //向servletContext设置根容器
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

所以说父容器要比子容器先初始化。

那么,DispatcherServlet在哪里创建的呢?

这就要看AbstractDispatcherServletInitializer类:

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
        //调用父类AbstractContextLoaderInitializer的onStartup
        //注册DispatcherServlet
		super.onStartup(servletContext);
		registerDispatcherServlet(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 null or empty");
		//创建Servlet容器(子容器)
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

		//创建DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		//增加servlet
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("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);
			}
		}

		//留给子类实现的方法:用于自定义ServletRegistration
		customizeRegistration(registration);
	}

这里创建了WebApplicationContext子容器,然后创建DispatcherServlet。创建了DispatcherServlet后,后面在DispatcherServlet初始化时候,会调用它的init()方法,又会走到FrameworkServlet类的initWebApplicationContext方法:

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());//获取父容器
		WebApplicationContext wac = null;//子容器
		//this.webApplicationContext 是之前createServletApplicationContext()创建的Servlet容器
		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;//当前web的IOC容器
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);//设置父子容器关系
					}
					//配置和刷新容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				//onRefresh方法模板,DispatcherServlet实现了改方法
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

至此父子容器关系建立,并且父容器和子容器刷新配置完成。

这就是SpringMVC父子容器创建的原理。

父子容器特点

  1. 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean
  2. 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean
  3. 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止
  4. 子容器中可以通过任何注入方式注入父容器中的bean,而父容器中是无法注入子容器中的bean,原因是第2点

相关文章: