初始入口:
org.springframework.web.context.ContextLoaderListener
org.springframework.web.context.ContextLoader
類的繼承關系圖如下:
可以看到這個Listener實現了Servlet標准提供的接口,這個listener只有兩個方法是 用來監聽web容器啟動和關閉動作的:
/** * Receives notification that the web application initialization * process is starting. * * <p>All ServletContextListeners are notified of context // 這里已經說了,使用的觀察者模式,容器啟動會調用 * initialization before any filters or servlets in the web * application are initialized. * * @param sce the ServletContextEvent containing the ServletContext * that is being initialized */ public void contextInitialized(ServletContextEvent sce); /** * Receives notification that the ServletContext is about to be * shut down. * * <p>All servlets and filters will have been destroyed before any * ServletContextListeners are notified of context * destruction. * * @param sce the ServletContextEvent containing the ServletContext //這里也說了,容器關閉會調用 * that is being destroyed */ public void contextDestroyed(ServletContextEvent sce);
當然ContextLoaderListener實現了這兩個方法:
/** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); //這個方法在父類ContextLoader中實現 ,這里只是把ServletContext當參數傳過去了,因為在執行邏輯中需要用到很多與web容器相關的參數 } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); //同上 }
切換到父類ContextLoader 中initWebApplicationContext方法中:
/** * Initialize Spring's web application context for the given servlet context, * using the application context provided at construction time, or creating a new one * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. * @param servletContext current servlet context * @return the new WebApplicationContext * @see #ContextLoader(WebApplicationContext) * @see #CONTEXT_CLASS_PARAM * @see #CONFIG_LOCATION_PARAM */ 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!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); 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); //這里先創建Web環境的 ApplicationContext ,這個Context的地位就類似於ServletContext,是獲取Bean的入口 } 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); //這才是最核心的方法,看名字就知道 配置並刷新 ApplicationContext,這個動作完成了類示例的創建,初始化和 注冊事件的發布,各種監聽的執行, 為 } //DispatcherServlet這個核心類執行Handler的注冊做好了萬全准備 } 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); //這里可以看到把當前Context綁定到當前線程,是為了做隔離使用,保證線程安全,稍后再說這個問題 } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
先來看看ApplicationContext的創建,進入createWebApplicationContext,畢竟它是獲取Bean的入口。
/** * Instantiate the root WebApplicationContext for this loader, either the * default context class or a custom context class if specified. * <p>This implementation expects custom contexts to implement the * {@link ConfigurableWebApplicationContext} interface. * Can be overridden in subclasses. * <p>In addition, {@link #customizeContext} gets called prior to refreshing the * context, allowing subclasses to perform custom modifications to the context. * @param sc current servlet context * @return the root WebApplicationContext * @see ConfigurableWebApplicationContext */ protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); 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); } /** * Return the WebApplicationContext implementation class to use, either the * default XmlWebApplicationContext or a custom context class if specified. * @param servletContext current servlet context * @return the WebApplicationContext implementation class to use * @see #CONTEXT_CLASS_PARAM * @see org.springframework.web.context.support.XmlWebApplicationContext */ protected Class<?> determineContextClass(ServletContext servletContext) { 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); } } }
這里先從ServletContext中獲取參數是contextClass的值,也就是從web.xml讀取contextParam或者硬編碼的方式(servlet3.0以上的版本)中獲取配置指定的ApplicationContext。如果沒有,使用默認的策略 XmlWebApplicationContext,這個默認的策略 defaultStrategies是在ContextLoader開頭的靜態代碼塊中初始的:
/** * Name of the class path resource (relative to the ContextLoader class) * that defines ContextLoader's default strategy names. */ private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); //這里從ContextLoader所在的包下加載一個叫ContextLoader.properties的文件,
//這個文件在spring-web包的context子包中,內容只有一行,配置了默認的WebApplicationContext defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
有了ApplicationContext了,再回到上面initWebApplicationContext方法,下面就開始判斷這個context是不是ConfigurableWebApplicationContext實例,如果是,並且沒有執行過刷新,就執行配置刷新的方法,也就是上面提到的最核心的方法,用於bean的初始化,
這里在判斷后,還獲取了parent context並將它設置成當前context的parent.
當然最核心的入口,一切源頭:
org.springframework.web.servlet.DispatcherServlet
該類的繼承關系(使用Idea分析工具)如下:
DefaultListableBeanFactory類的繼承關系:
與Bean創建時相關的Aware接口的三個直接子接口: