在web項目中使用spring的時候,咱們會在web.xml中加入以下配置:html
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
這個配置增長了一個listener,這個ContextLoaderListener 實現了ServletContextListener 。咱們在平常工做中也會定義一些listener。用於在應用啓動的時候作些什麼。由於咱們知道servelt容器在啓動的時候,Listener 類中的contextInitialized()方法將會被調用。spring中ContextLoaderListener也把這個做爲起始點來初始化,contextInitialized()方法的實現以下:java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { private ContextLoader contextLoader; public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader();//此方法直接return null了 if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); } @Deprecated protected ContextLoader createContextLoader() { return null; } }
根據上面的代碼,在調用contextInitialized方法裏邊的代碼不多,先是給contextLoader賦值了,而後調用了initWebApplicationContext方法。這個方法就是咱們窺探的入口,它是在ContextLoader類中的,代碼以下,能夠先不要讀這個代碼,大概掃一眼,而後繼續根據後面的文字描述跟蹤邏輯:web
//爲了方便初步的閱讀,我刪除了一些佔用篇幅的地方 public class ContextLoader { public static final String CONTEXT_CLASS_PARAM = "contextClass"; public static final String CONTEXT_ID_PARAM = "contextId"; public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses"; public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";//web.xml裏有,熟悉吧 public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; private static final Properties defaultStrategies; static { //這個靜態代碼塊讀取了ContextLoader.properties這個配置文件。在spring源碼中能夠找到這個配置文件 //內容只有一行 指定WebApplicationContext的實現類爲XmlWebApplicationContext try { ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } } private static volatile WebApplicationContext currentContext; private WebApplicationContext context; private BeanFactoryReference parentContextRef; public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 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 { if (this.context == null) { // 這個create方法建立了一個ConfigurableWebApplicationContext實例 this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) {//isActive默認爲false,因此會進入if,執行下面的代碼 if (cwac.getParent() == null) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } // configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute("一個變量名", 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.isInfoEnabled()) { long t = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in"+t+" ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute("一個很長變量名", ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute("一個很長變量名", err); throw err; } } }
在上面的貼出的的代碼中,咱們能夠看到這個ContextLoader類有一個靜態代碼塊,靜態代碼塊會在累加在的時候就執行了,這個代碼塊執行的內容很簡單,就是找到一個名爲「ContextLoader.properties」的配置文件,並將這個Properties賦給defaultStrategies變量。ContextLoader.properties的內容以下:spring
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
咱們繼續看initWebApplicationContext方法。這個方法很長,可是實際上它主要作了兩件事情:app
1.建立一個ConfigurableWebApplicationContext實例。ide
2.根據建立的ConfigurableWebApplicationContext實例,來配置並刷新WebApplicationContext。源碼分析
先看第一步,建立ConfigurableWebApplicationContext,方法是createWebApplicationContext()。代碼以下:post
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("日誌描述"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
這個方法先調用determineContextClass來找到一個類,而後return語句中經過BeanUtils反射來建立這個類的實例並返回。this
determineContextClass類中,其實就是利用剛纔讀到的配置文件「ContextLoader.properties」,從這個文件中獲得配置的類名,根據類名返回Class對象。代碼簡單就不貼了。日誌
在看第二步,刷新WebApplicationContext。即調用了configureAndRefreshWebApplicationContext(...)方法。這個方法裏邊作的事情很重要。先看看代碼:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // 這裏我刪除了一段可有可無的代碼,起邏輯和下面的兩句同樣。 String xxx= ...; wac.setId(xxx) } wac.setServletContext(sc); // 這裏獲得了咱們在web.xml中配置的一個值,即:contextConfigLocation的值,也就是咱們的spring文件 String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { // 設置spring的配置文件 wac.setConfigLocation(initParameter); } customizeContext(sc, wac);//後面分析 wac.refresh();//後面分析 }
方法中,最後兩行的方法調用。先看customizeContext(sc,wac)方法,方法的裏邊經過serveltContext 的getInitParameter方法獲得contextInitializerClasses。若是沒配置,就什麼也不作,進入以後,能夠看到頭兩行的內容以下:
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(servletContext); if (initializerClasses.size() == 0) { // no ApplicationContextInitializers have been declared -> nothing to do return; }
第一行,經過determineContextInitializerClasser方法來獲取配置的contextInitializerClasses變量值,這個值是一個class類名,多個的話用逗號隔開。若是沒有配置的話,代碼就會執行if size=0這一句,而後return了。
第二行wac.refresh()方法調用很是重要。這個方法實際上完成了spring 容器的初始化,代碼以下:
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) { destroyBeans();cancelRefresh(ex); throw ex; } } }
至此spring容器初始化完成了,這個wac.refresh()代碼暫時不作深究,本篇主要討論的是web.xml中的一行配置如何致使了spring的啓動過程。本文代碼是基於spring3.2.5RELASE。
SpringMVC是怎麼工做的,SpringMVC的工做原理
spring 異常處理。結合spring源碼分析400異常處理流程及解決方法
Netty系列
Mybatis Mapper接口是如何找到實現類的-源碼分析