spring IOC
一直想阅读一下spring的源码,今天终于大致理清了spring IOC容器的初始化整个的脉络,做个记录。。。
在开源的世界里,spring无疑是典范级别的,在项目中也是经常使用,所以学习spring优雅的设计对于提升做项目的能力,以及自我能力的提升都是极好的,在网上也看了很多相关的材料,所以就结合spring在web中的运用,逐步分析一下spring IOC容器初始化的整个过程。
废话不多说,上干货。。。。
在web项目的web.xml中,我们已经非常熟悉如何引用spring容器,代码如下。
<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接管的开始。
下面就具体讲讲spring如何完成初始化
首先我们看看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方法来做,那这个方法具体做了什么了,请继续往下看代码
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; } }
在此方法中,做了两件事,如代码中注释的两部分,1、初始化执行环境上下文context , 2、初始化。
下面我们分别来稍微仔细看下spring如何做这两件事,首先是创建上下文context,同样上代码
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容器的初始化工作也将完成。