一直想閱讀一下spring的源碼,今天終於大體理清了spring IOC容器的初始化整個的脈絡,作個記錄。。。node
在開源的世界裏,spring無疑是典範級別的,在項目中也是常常使用,因此學習spring優雅的設計對於提高作項目的能力,以及自我能力的提高都是極好的,在網上也看了不少相關的材料,因此就結合spring在web中的運用,逐步分析一下spring IOC容器初始化的整個過程。web
廢話很少說,上乾貨。。。。spring
在web項目的web.xml中,咱們已經很是熟悉如何引用spring容器,代碼以下。app
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
因此我學習spring IOC時就以此處爲入口,去查看ContextLoaderListener。該類繼承於ContextLoader,實現了ServletContextListener,ServletContextListener是J2EE中ServletContext生命週期事件監聽接口,因此在web應用啓動後,ContextLoaderListener監聽到相關事件(容器啓動事件),調用contextInitialized()方法,執行初始化,這也是spring IOC接管的開始。ide
下面就具體講講spring如何完成初始化post
首先咱們看看ContextLoaderListener的contextInitialized()方法, 上代碼學習
/** * Initialize the root web application context. */ public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); }
該方法中首先初始化了contextLoader,而後調用contextLoader的initWebApplicationContext方法,因此spring IOC容器的初始化工做主要交給了ContextLoaderListener的父類ContextLoader的initWebApplicationContext方法來作,那這個方法具體作了什麼了,請繼續往下看代碼this
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); } 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); } } 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; } }
在此方法中,作了兩件事,如代碼中註釋的兩部分,一、初始化執行環境上下文context , 二、初始化。lua
下面咱們分別來稍微仔細看下spring如何作這兩件事,首先是建立上下文context,一樣上代碼spa
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); }
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); } } }
這兩段代碼就是spring主要用來建立上下文的, 當咱們不曾提供上下文類時,spring經過defaultStrategies加載默認上下文,默認的上下文信息配置在ContextLoader.properties文件中,經過該文件咱們能夠看到,spring默認的上下文環境就是XmlWebApplicationContext的實例,因此spring在默認狀況下經過
ContextLoader.properties文件初始化上下文爲XmlWebApplicationContext的實例。
spring已經完成了第一件事建立上下文實例,那咱們來看第二件事,也是spring IOC容器初始化過程當中最重要的事,在看以前先上一張XmlWebApplicationContext類的繼承關係圖,便於後面理解spring IOC的初始化過程
話接前面,上面說過spring首先建立了上下文context,爲XmlWebApplicationContext的實例,XmlWebApplicationContext的繼承關係如上圖,下面咱們就來看spring最重要的一件事初始化,首先上前面initWebApplicationContext方法調用的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 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { // Servlet <= 2.4: resort to name specified in web.xml, if any. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getServletContextName())); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } } wac.setServletContext(sc);
//配置信息 String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { wac.setConfigLocation(initParameter); } customizeContext(sc, wac);
//委託給上下文執行初始化 wac.refresh(); }
經過這段代碼能夠看出,spring實例化上下文後,開始配置上下文信息,最重要的就是讀取配置文件位置,而後委託給上下文執行初始化,spring執行初始化的過程採用的模版方法進行初始化的,如上面代碼標紅的refresh方法定義在AbstractApplicationContext,經過上面的類繼承關係能夠看到該類。下面咱們來看看該模版方法
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) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
該方法定義了spring IOC容器初始化的整個流程,稍微說一句,給方法取個好名字多重要呀,看着spring這些方法取的名字就大概知道這個方法要作什麼事情了,
爲了從宏觀層面去了解spring IOC容器初始化的過程,這裏就不對spring初始化的模版方法裏面的每一個方法進行說明了,只是主要說明一下讀取配置文件中Bean定義文件的解析。
看到上面代碼中標紅的obtainFreshBeanFactory方法,該方法源碼以下:
/** * Tell the subclass to refresh the internal bean factory. * @return the fresh BeanFactory instance * @see #refreshBeanFactory() * @see #getBeanFactory() */ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
該方法中調用的refreshBeanFactory方法,refreshBeanFactory方法是一個抽象方法,定義在AbstractApplicationContext類中,而後AbstractRefreshableApplicationContext進行了實現,經過名稱也就知道該方法是作什麼事情的了吧,看下該方法的源碼
@Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
在該方法中咱們看到有以下標紅的一句代碼,這也是spring IOC讀取Bean定義的正真入口,在初始化spring IOC容器後,讀取Bean定義文件並解析,看下該方法的源碼
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
在該方法中初始化了一個XmlBeanDefinitionReader 讀取類,設置好該類的相關屬性後,經過該類的loadBeanDefinitions方法讀取bean定義,該方法委託給doLoadBeanDefinitions方法,doLoadBeanDefinitions又委託給registerBeanDefinitions方法,而後由該方法進行具體的bean定義讀取,registerBeanDefinitions方法源碼以下
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
在該方法中初始化了一個BeanDefinitionDocumentReader類,該類主要真正負責的讀取bean定義文件,將讀取到的bean定義信息委託給BeanDefinitionParserDelegate去解析,下面先看下初始化BeanDefinitionDocumentReader後,交由BeanDefinitionDocumentReader作了哪些事,看下上圖中標紅方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
能夠看到該方法委託給了doRegisterBeanDefinitions方法來作讀取bean定義文件的信息,接着看doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!this.environment.acceptsProfiles(specifiedProfiles)) { return; } } BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
該方法經過三個模版方法preProcessXml,parseBeanDefinitions,postProcessXml對配置文件進行處理,其中preProcessXml,postProcessXml爲鉤子方法,咱們能夠在子類重寫這兩個方法進行配置文件預處理與後處理,解析bean定義文件的工做真正由parseBeanDefinitions來完成,查看該方法源碼
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
該方法讀取配置文件的每一個結點,而後委託給BeanDefinitionParserDelegate類,來具體進行Bean的定義具體解析,經過遞歸調用首先讀取import引入文件中的bean定義。整個spring讀取bean定義的過程也就到此結束。最後上一下BeanDefinitionParserDelegate類解析bean定義的方法parseBeanDefinitionElement
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
總結:
spring IOC容器初始化過程主要經過BeanDefinitionReader完成Bean定義的解析工做,同時將真正解析bean定義文檔的工做委託給BeanDefinitionDocumentReader來完成,而後BeanDefinitionDocumentReader讀取文件結束後將bean定義註冊工做交由BeanDefinitionParserDelegate來具體完成,BeanDefinitionParserDelegate完成驗證bean定義相關操做後,經過回調容器的註冊接口將全部的Bean定義完成註冊,至此Bean定義解析工做完成,spring IOC容器的初始化工做也將完成。