Spring和SpringMVC的父子容器关系

320 阅读2分钟

在看Spring的IOC源码时,看到了一个接口HierarchicalBeanFactory,该接口是为了获取父级容器。因此就去了解了一下spring为啥会用到父子容器。其中一个很典型的例子就是Spring和SpringMVC整合的web项目。

首先,需要了解一下什么是父子容器: 简单来说,父子容器中都有很多初始化的bean,子容器可以通过getBea()获取到父层级的bean,而父容器获取不到子容器的bean(类似于java的继承)。这样做的好处就是依赖分层明确,职责不混乱。 其次,我们看一下在整合项目中是如何实现父子容器的。在springMVC项目中,web.xml中需要配置如下一段代码

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext*.xml</param-value>
  </context-param>
 
<!--创建父容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 
<!-- 创建子容器-->
  <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-*.xml</param-value>
    </init-param>
  </servlet>
web项目启动的顺序是context-param->listener->filter->servlet

在ContextLoaderListener类中有一个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!");
		}
		...
		try {
			// 创建根容器
			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
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			// 判断线程上下文类加载器是否是WebAppClassLoader
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}
			...
			return this.context;
		}
		...
	}

之后在DispatcherServlet的初始化的时候,它会调FrameworkServlet#initWebApplicationContext方法,在该方法中进行了子容器的创建。

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		if (this.webApplicationContext != null) {
			// context实例在构造时被注入
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					//设置父上下文
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			//查找web容器
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// 创建web容器,在该方法中也设置了容器的父
			wac = createWebApplicationContext(rootContext);
		}
		...
		return wac;
	}

最后,也去思考了几个问题。

  1. springMVC的加载需要DispatcherServlet,但是一定需要ContextLoaderListener吗? 不一定需要(参考blog.csdn.net/gzu_imis/ar… 但是为了更好的区分各层级bean的业务含义,一般是用子容器去加载controller层的bean;父容器去加载service和dao层的bean,这样可以保证在service层不会乱用controller层的bean。