Spring版本 4.3.2,ssm框架java
代碼過寬,能夠shift + 鼠標滾輪 左右滑動查看web
<!--配置獲取項目的根路徑,java類中使用System.getProperty("web.root")--> <context-param> <param-name>webAppRootKey</param-name> <param-value>web.root</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.WebAppRootListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
IOC容器初始化從ContextLoaderListener類開始,這個監聽器是ServletContextListener的子類,在web.xml配置後會被容器(爲避免和IOC容器混淆,後文用tomcat代稱)調用。該類繼承關係以下:spring
tomcat初始化ContextLoaderListener類時會先調用其父類ContextLoader的靜態代碼塊數據庫
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. // 加載屬性文件默認策略的實現,當前處於徹底內部環境,不能由開發者去自定義 try { // DEFAULT_STRATEGIES_PATH 常量,指向該類同一目錄層級下的ContextLoader.properties文件 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); //用流讀取文件中的key-value對,在這個地方肯定默認上下文的Class defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
ContextLoader.properties文件中內容:數組
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
默認策略讀取完成,此策略肯定未指定上下文時的默認類型,爲XmlWebApplicationContext。tomcat
若是web.xml中有配置WebAppRootListener監聽器,根據web.xml中的順序,先調用WebAppRootListener的contextInitialized方法。app
public void contextInitialized(ServletContextEvent event) { WebUtils.setWebAppRootSystemProperty(event.getServletContext()); } public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException { Assert.notNull(servletContext, "ServletContext must not be null"); //獲取項目在主機上的完整路徑root String root = servletContext.getRealPath("/"); if (root == null) { throw new IllegalStateException( "Cannot set web app root system property when WAR file is not expanded"); } //WEB_APP_ROOT_KEY_PARAM爲常量:webAppRootKey。在web.xml中獲取key爲webAppRootKey的value值 String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM); //DEFAULT_WEB_APP_ROOT_KEY爲常量:webapp.root,如沒有指定WEB_APP_ROOT_KEY_PARAM則取此常量 String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY); //無論拿的是DEFAULT_WEB_APP_ROOT_KEY仍是WEB_APP_ROOT_KEY_PARAM,以其value做爲key,找到對應的root String oldValue = System.getProperty(key); //確保root惟一 if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) { throw new IllegalStateException( "Web app root system property already set to different value: '" + key + "' = [" + oldValue + "] instead of [" + root + "] - " + "Choose unique values for the 'webAppRootKey' context-param in your web.xml files!"); } //設置root System.setProperty(key, root); servletContext.log("Set web app root system property: '" + key + "' = [" + root + "]"); }
WebAppRootListener監聽器初始化任務結束,而後調用ContextLoaderListener監聽器的contextInitialized方法框架
/** * Initialize the root web application context. * * 初始化根上下文 */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext方法在ContextLoaderListener的父類ContextLoader中webapp
/** * 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. * * 經過給定的 servlet context 初始化 Spring 的 web application context, * 這個 application context 要麼是在構造時被提供,要麼是根據 contextClass * 和 contextConfigLocation 兩個上下文參數從新建立的。 */ public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // servlet context 中只能有一個 root web application context。 // root web application context 初始化完成後會放入 servlet context 中 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { // 不能初始化 context ,由於已經有一個 root web application context存在 // 檢查你是否在你的web.xml中定義了多個ContextLoader 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); // 初始化 spring root WebApplicationContext servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { // Root WebApplicationContext 初始化開始 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. // 在這個實例變量中保存 context, // 以確保在 ServletContext 關閉時可以用到 if (this.context == null) { // 1.建立 WebApplicationContext this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; // 默認 WebApplicationContext 沒有激活 if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc // 此時 context 尚未被刷新 -> 提供設置 parent context 、application context id // 等服務。 if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. // 2.這個 context 實例被注入時沒有一個明確的 parent -> 若是有的話, // 須要爲 root web application context 肯定 parent ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } // 3.配置並刷新 WebApplicationContext(超級巨大的方法) configureAndRefreshWebApplicationContext(cwac, servletContext); } } //將 root web application context 添加到 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.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; } }
從標1的方法開始,這個方法用來建立 root web application context ,並用ContextLoader的context變量接收ide
// 1.建立 WebApplicationContext this.context = createWebApplicationContext(servletContext); // 看下ContextLoader類的context屬性 /** * The root WebApplicationContext instance that this loader manages. * * 由ContextLoader管理的 root web application context. */ private WebApplicationContext context; /** * 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. * * 爲這個 loader 實例化 root WebApplicationContext , * 要用採用默認的 context class,要麼使用指定的自定義的 context class。 * 對於自定義的 contexts class 來講, * 但願實現 ConfigurableWebApplicationContext 接口,也能夠重寫他的子類。 * 另外,customizeContext方法 會在 context 被刷新前調用, * 容許子類執行對 context 的自定義修改。 */ protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 肯定 Context Class Class<?> contextClass = determineContextClass(sc); // 確保自定義 context 是 ConfigurableWebApplicationContext 的子類 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } // 實例化 root WebApplicationContext return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } /** * Return the WebApplicationContext implementation class to use, either the * default XmlWebApplicationContext or a custom context class if specified. * * 返回 WebApplicationContext 接口的實現, * 要麼使用默認的 XmlWebApplicationContext ,要麼使用自定義的 context */ protected Class<?> determineContextClass(ServletContext servletContext) { // CONTEXT_CLASS_PARAM -> contextClass // 若是web.xml中配置了contextClass這個屬性,那麼就取自定義context Class 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 { // 若是沒有配置自定義 context Class, // 則取默認策略中的 context Class,也就是 XmlWebApplicationContext 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); } } }
這樣標1的方法也就執行完了。看一下XmlWebApplicationContext的繼承關係:
跟蹤標記2的方法。
此方法在ContextLoader類中實現。
// 2.這個 context 實例被注入時沒有一個明確的 parent -> 若是有的話, // 須要爲 root web application context 肯定 parent ApplicationContext parent = loadParentContext(servletContext); /** * Template method with default implementation (which may be overridden by a * subclass), to load or obtain an ApplicationContext instance which will be * used as the parent context of the root WebApplicationContext. If the * return value from the method is null, no parent context is set. * <p>The main reason to load a parent context here is to allow multiple root * web application contexts to all be children of a shared EAR context, or * alternately to also share the same parent context that is visible to * EJBs. For pure web applications, there is usually no need to worry about * having a parent context to the root web application context. * <p>The default implementation uses * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context * which will be shared by all other users of ContextsingletonBeanFactoryLocator * which also use the same configuration parameters. * * 一個默認實現的模板方法(可能被子類覆蓋), * 用來加載和得到一個 ApplicationContext 的實例,這個實例被用來 * 做爲 root WebApplicationContext 的 parent context 。 * 若是此方法返回的是null,那麼 root WebApplicationContext 就沒有 parent context 。 * 加載 parent context 的主要緣由是爲了讓多個 root web application contexts * 成爲共享 EAR context 的 children, * 或者共享一個對 EJBs 可見的 parent context。 * 爲了web applications 的純潔性,一般不須要關心 root web application context 的 parent context。 * 默認實現使用 ContextSingletonBeanFactoryLocator,經過兩個參數去配置,去加載一個被全部 * ContextsingletonBeanFactoryLocator 其餘用戶所共享的 parent context,它們也使用同樣的配置參數。 */ protected ApplicationContext loadParentContext(ServletContext servletContext) { ApplicationContext parentContext = null; String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); // 這裏沒有配置這兩個參數,因此此 context 沒有 parent if (parentContextKey != null) { // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isDebugEnabled()) { logger.debug("Getting parent context definition: using parent context key of '" + parentContextKey + "' with BeanFactoryLocator"); } this.parentContextRef = locator.useBeanFactory(parentContextKey); parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; }
跟蹤標記3的方法。
此方法在ContextLoader類中實現。
// 3.配置並刷新 WebApplicationContext(超級巨大的方法) configureAndRefreshWebApplicationContext(cwac, servletContext); protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { // 在類AbstractApplicationContext中,定義了 context 的id // private String id = ObjectUtils.identityToString(this); // id是 context 的惟一標識 // identityToString 方法返回的是 context 的類名 + "@" + 十六進制的哈希值 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 // 這個 application context id 仍然設置爲他的初始默認值-->基於可利用的信息分配給他一個更有用的id // 若是 servlet context 中設置了 CONTEXT_ID_PARAM -> contextId屬性, // 那麼就採用這個做爲id String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... // 不然生成默認的id // "org.springframework.web.context.WebApplicationContext:" + 項目名 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); // CONFIG_LOCATION_PARAM -> contextConfigLocation, // 從sevletContext中拿的這個屬性,就是咱們常常在web.xml中配置的屬性: // classpath:spring/applicationContext.xml String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { // 以數組的形式將 contextConfigLocation 保存在 // root web application context 的 configLocations 屬性中, // 保存時會先對路徑進行解析,替換佔位符,而這個操做須要 Environment 對象來完成。 // context 中有屬性 environment,environment 默認爲 StandardServletEnvironment 實現, // 實例化 StandardServletEnvironment 時其父類 AbstractEnvironment // 會調用 customizePropertySources 方法, // 這個方法會將 systemEnvironment、systemProperties 啥的鍵值對以及jndiProperties保存在實例中, // 後續還會將 servletContextInitParams 、servletConfigInitParams 等屬性保存進來 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 // 當 context 被刷新時,wac 的 environment 的 initPropertySources 方法在任何狀況下都會被調用。 // 之因此這麼急切的調用是爲了確保 servlet 屬性源在一些 post-processing 中或者發生在refresh方法 // 以前的初始化中可以到位使用。 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { //將 servletContextInitParams、servletConfigInitParams 等屬性保存進 environment 對象中 ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 3.1對 Context 的自定義操做 customizeContext(sc, wac); // 3.2整個IOC的重點,內容很是多,裏面的每一個方法都會分一篇文章單獨講 wac.refresh(); }
跟蹤標記3.1的方法。
此方法在ContextLoader類中實現。
// 3.1對上下文的自定義操做 customizeContext(sc, wac); /** * Customize the {@link ConfigurableWebApplicationContext} created by this * ContextLoader after config locations have been supplied to the context * but before the context is <em>refreshed</em>. * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext) * determines} what (if any) context initializer classes have been specified through * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and * {@linkplain ApplicationContextInitializer#initialize invokes each} with the * given web application context. * <p>Any {@code ApplicationContextInitializers} implementing * {@link org.springframework.core.Ordered Ordered} or marked with @{@link * org.springframework.core.annotation.Order Order} will be sorted appropriately. * * 當 config locations 被提供給 context 後, * 由此 ContextLoader 建立的 ConfigurableWebApplicationContext 能夠進行自定義化, * 可是必須在 context 被刷新以前進行。 * 這個默認的實現,determineContextInitializerClasses 方法, * 經過兩個參數以及給定的 web application context 肯定了 context initializer classes,這兩個參數分別是 * CONTEXT_INITIALIZER_CLASSES_PARAM(上下文初始化參數)和ApplicationContextInitializer(調用每個)。 * 任何實現了 Ordered 或者有 Order 註解的 ApplicationContextInitializers 都將被適當的排序 */ protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { // 3.1.1web.xml中沒有自定義參數,因此此處返回空list List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); // 沒有自定義操做,因此跳過。有則實例化 Initializer for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); 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())); } this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } // 若是有多個 contextInitializers,會根據 Order 作一個排序 AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { // contextInitializers 對 context 進行初始化 initializer.initialize(wac); } }
跟蹤標記3.1.1的方法。
此方法在ContextLoader類中實現。
// 3.1.1web.xml中沒有定義,因此此處返回空list List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); /** * Return the {@link ApplicationContextInitializer} implementation classes to use * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. * * 若是指定了 CONTEXT_INITIALIZER_CLASSES_PARAM 參數, * 將會返回 ApplicationContextInitializer 接口的實現類的Class對象以供使用 */ protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses(ServletContext servletContext) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>(); //是否有自定義GLOBAL_INITIALIZER_CLASSES_PARAM參數,沒有跳過 String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } //是否有自定義CONTEXT_INITIALIZER_CLASSES_PARAM參數,沒有跳過 String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); if (localClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } //web.xml中沒有定義,因此此處返回空list return classes; }
跟蹤標記3.2的方法。
此方法在AbstractApplicationContext類中實現。
// 3.2刷新操做 wac.refresh(); /** * Load or refresh the persistent representation of the configuration, * which might an XML file, properties file, or relational database schema. * <p>As this is a startup method, it should destroy already created singletons * if it fails, to avoid dangling resources. In other words, after invocation * of that method, either all or no singletons at all should be instantiated. * * 加載或者刷新配置的持久性表示, * 這些配置可能在xml文件上,properties文件上,或者相關數據庫上。 * 由於這是一個啓動方法,因此若是他失敗的話那麼已經被建立的單例會被銷燬,避免佔用資源。 * 換句話說,在這個方法被調用以後,要麼全部單例都被實例化,要麼所有都沒有。 */ @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
這上面的方法會一個個跟蹤。 未完····
參考: prepareRefresh方法:https://www.jianshu.com/p/c7364aeb0443 obtainFreshBeanFactory方法:https://www.jianshu.com/p/144af98965d9 prepareBeanFactory方法:https://www.jianshu.com/p/3468118a31f9 postProcessBeanFactory方法:https://www.jianshu.com/p/c05aea93b939
——————————————————————————————————
——————————————————————————————————