Spring 容器與 Servlet互動

曲終人散121發表於2020-12-28

1 Servlet容器如何啟動spring容器的?

在serlet容器中的 web.xml中配置’ Spring 容器的相關配置:


<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-config.xml</param-value>
</context-param>



//1 方法一
<listener>
 <listener-class>org.springframework.context.ContextLoaderListener</listener-class>
</listener>


//2 方法二 3.0也已經發布宣告說,已經移除了
<servlet>
 <servlet-name>context</servlet-name>
 <servlet-class>org.springframework.context.ContextLoaderServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>

1.1 Listener方式

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
實現了ServletContextListener,是serlet容器的規範,容器會回撥入contextInitialized方法。

	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

org.springframework.web.context.ContextLoader#initWebApplicationContext

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			// 1 建立webapplication
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			//2 重新整理容器
			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);
				}
			}
			//3 在serlet容器中,儲存ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE與webapplication容器建值對
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            //4 設定類載入器與容器引用的關係。  
			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;
		}

contextConfigLocation 引數配置檔案的路徑,CONFIG_LOCATION_PARAM是在configureAndRefreshWebApplicationContext方法中傳入的。

1.2 Servlet方式

已廢棄

2 SpringMVC 容器的載入

  <!--載入springMVC-->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springcfg/springMVC.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

DispatcherServlet繼承了httpservlet

	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

DispatcherServlet 的父類,該方法是載入 SpringMVC 容器,org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext:

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			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(wac);
			}
		}

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

		return wac;
	}

DispatcherServlet 此時作為一個 Bean,實現了 ApplicationContextAware 介面,會自動將上下文環境儲存到 webApplicationContext 欄位中;

rootContext 和 webApplicationContext 區別?

rootContext是當前servlet容器中的spring 中的webApplicationcontext。是springMVC的父容器的Context。
webApplicationContext載入DispatcherServlet spring容器的webApplicationcontext。

3 springBoot

1、在 Springboot 應用程式啟動時,在 SpringBootServletInitializer#onStartup 方法中,會建立一個 rootAppContext 容器,如下:

同時將上文所說的 ContextLoaderListener 監聽器新增到 Servlet 容器中,同樣達到了 xml 配置的效果,而調createRootApplicationContext 方法建立 rootAppContext 容器時,會將 contextClass 設定為 AnnotationConfigServletWebServerApplicationContext.class。

相關文章