打算開始讀一些框架的源碼,先拿 Spring MVC 練練手,歡迎點擊這裏訪問個人源碼註釋, SpringMVC官方文檔一開始就給出了這樣的兩段示例:html
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletCxt) { // Load Spring web application configuration AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); ac.refresh(); // Create and register the DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }
<web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-context.xml</param-value> </context-param> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
咱們按照 web.xml 中的實例來看一下 Spring MVC 初始化過程.git
Spring MVC 的上下文有以下這樣的層級:github
圖中的 Servlet WebApplicationContext
是與 DispatcherServlet
綁定的上下文, 其中還有 controllers、ViewResolver、HandlerMapping 等組件.web
Root WebApplicationContext
不是必須的上下文, 在須要時,能夠用來在多個 DispatcherServlet
間共享一些 bean.spring
web.xml 中配置的 ContextLoaderListener
用於啓動和終止 Spring 的 root WebApplicationContext
.spring-mvc
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. * Servlet 上下文初始化,調用父類的方法初始化 WebApplicationContext */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. * Servlet 上下文被銷燬,調用父類的方法銷燬 WebApplicationContext */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); // 銷燬 ServletContext 上實現了 DisposableBean 的屬性並移除他們 ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
ContextLoaderListener
直接調用了父類 ContextLoader
的方法來初始化和銷燬上下文.mvc
ContextLoaderListener
在建立上下文時,會嘗試讀取 contextClass
context-param 來指定上下文的類型,被指定的類須要實現 ConfigurableWebApplicationContext
接口. 若是沒有獲取到,默認會使用 WebApplicationContext
.app
初始化上下文時,會嘗試讀取 contextConfigLocation
context-param, 做爲 xml 文件的路徑.框架
initWebApplicationContext()
方法以下:webapp
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. // 保存上下文到本地實例變量中,保證上細紋能在 ServletContext 關閉時訪問到 if (this.context == null) { // 建立上下文 this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { // 若是上下文實現了 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 的屬性上 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) { // 若是有線程上下文類加載器,並且不是 ContextLoader 自己的類加載器,放入到 currentContextPerThread 中。這是一個 static 的域 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; } }
initWebApplicationContext()
方法調用了 createWebApplicationContext()
方法來建立上下文;調用了 configureAndRefreshWebApplicationContext()
來對實現了 ConfigurableWebApplicationContext
接口的上下文作初始化.
createWebApplicationContext()
會調用 determineContextClass()
來獲取上下文類型的 Class 對象.
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 獲取上下文類型 Class<?> contextClass = determineContextClass(sc); // 檢查是否實現了 ConfigurableWebApplicationContext if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } // 實例化 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } protected Class<?> determineContextClass(ServletContext servletContext) { // 獲取 serveltContext 的 'contextClass' 初始化參數。 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { // 指定過上下文類型,加載類 try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { // 去默認策略裏獲取默認的上下文類型名稱 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { // 加載類 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } }
configureAndRefreshWebApplicationContext()
方法.
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { // 用能夠獲取到的信息,獲取一個更有意義的上下文 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information // 獲取 ServletContext 的 'contextId' 初始化參數。 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... // 生成默認 id wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } // 設置 servletContext 屬性 wac.setServletContext(sc); // 設置配置文件路徑 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh // 初始化屬性源, 確保 servlet 屬性源到位並可以在任何 refresh 以前的後期處理和初始化中使用 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 在設置了配置文件以後上下文刷新以前,自定義上下文 customizeContext(sc, wac); wac.refresh(); } protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { // 根據 ServletContext 的 'contextInitializerClasses' 和 'globalInitializerClasses' 初始化參數 加載 ApplicationContextInitializer 的 class List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { // 獲取範型參數類型 Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); // 檢查 Initializer 是否適用於當前上下文對象 if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException(String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } // 建立 Initializer 實例,並添加到 contextInitializers this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } // 根據 org.springframework.core.Ordered 和 org.springframework.core.annotation.Order 排序,若是沒有實現或註解,會被排到最後 AnnotationAwareOrderComparator.sort(this.contextInitializers); // 執行每一個 initializer 的 initialize() 方法 for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } }
closeWebApplicationContext()
方法以下:
public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root WebApplicationContext"); try { // 若是 context 是 ConfigurableWebApplicationContext 調用 close() 方法 if (this.context instanceof ConfigurableWebApplicationContext) { ((ConfigurableWebApplicationContext) this.context).close(); } } finally { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = null; } else if (ccl != null) { currentContextPerThread.remove(ccl); } // 移除 servletContext 中的 context 屬性 servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } }
Servlet 3.0+ 中能夠經過 ServletContext
的 addlistener()
方法來添加監聽器.所以能夠先把 Spring 容器先建立好,再傳給 ContextLoaderListener
的構造器.這裏就不本身寫例子了,選了單元測試中的 ContextLoaderTests.testContextLoaderListenerWithDefaultContext()
方法:
public void testContextLoaderListenerWithDefaultContext() { MockServletContext sc = new MockServletContext(""); sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "/org/springframework/web/context/WEB-INF/applicationContext.xml " + "/org/springframework/web/context/WEB-INF/context-addition.xml"); ServletContextListener listener = new ContextLoaderListener(); ServletContextEvent event = new ServletContextEvent(sc); listener.contextInitialized(event); String contextAttr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; WebApplicationContext context = (WebApplicationContext) sc.getAttribute(contextAttr); assertTrue("Correct WebApplicationContext exposed in ServletContext", context instanceof XmlWebApplicationContext); assertTrue(WebApplicationContextUtils.getRequiredWebApplicationContext(sc) instanceof XmlWebApplicationContext); LifecycleBean lb = (LifecycleBean) context.getBean("lifecycle"); assertTrue("Has father", context.containsBean("father")); assertTrue("Has rod", context.containsBean("rod")); assertTrue("Has kerry", context.containsBean("kerry")); assertTrue("Not destroyed", !lb.isDestroyed()); assertFalse(context.containsBean("beans1.bean1")); assertFalse(context.containsBean("beans1.bean2")); listener.contextDestroyed(event); assertTrue("Destroyed", lb.isDestroyed()); assertNull(sc.getAttribute(contextAttr)); assertNull(WebApplicationContextUtils.getWebApplicationContext(sc)); }
轉自:https://www.cnblogs.com/FJH1994/p/10798028.html