Spring 源碼分析-1-啓動

Spring 源碼分析-1-啓動

在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接口是如何找到實現類的-源碼分析

相關文章
相關標籤/搜索