Spring的爬坑之路(二)ClassPathXmlApplicationContext


首先呢,我们通过debug跟踪代码的方式,首先去了解一下我们常用到的一些逻辑情况,spring逻辑足够庞大,对于第一次阅读甚至是第一次尝试阅读源码的我来说,这也是我想到的一个好的方法…

AppTest

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring.xml");

ClassPathXmlApplicationContext

  • ClassPathXmlApplicationContext的构造方法
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
        super(parent); // null
        this.setConfigLocations(configLocations); // spring.xml
        if (refresh) { // true
            this.refresh();
        }

    }

方法中主要有三个步骤:
1- 初始化属性信息
2- setConfigLocations主要是加载Spring配置文件的位置
3- 更新beanFactory

一、super(parent)
  • AbstractApplicationContext的构造。
  public AbstractApplicationContext() {
        this.logger = LogFactory.getLog(this.getClass());
        this.id = ObjectUtils.identityToString(this);
        this.displayName = ObjectUtils.identityToString(this);
        this.beanFactoryPostProcessors = new ArrayList();
        this.active = new AtomicBoolean();
        this.closed = new AtomicBoolean();
        this.startupShutdownMonitor = new Object();
        this.applicationListeners = new LinkedHashSet();
        this.resourcePatternResolver = 		  this.getResourcePatternResolver();
    }
  • AbstractApplicationContext.setParent方法
    public void setParent(ApplicationContext parent) { 
        this.parent = parent;
        if (parent != null) {
            Environment parentEnvironment = parent.getEnvironment();
            if (parentEnvironment instanceof ConfigurableEnvironment) {
                this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
            }
        }

    }
二、 setConfigLocations
   /**
    * spring 原文注视:
    * Set the config locations for this application context.
    * <p>If not set, the implementation may use a default as appropriate.
    */
   public void setConfigLocations(@Nullable String... locations) { // spring.xml
   	if (locations != null) {
   		Assert.noNullElements(locations, "Config locations must not be null");
   		this.configLocations = new String[locations.length];
   		for (int i = 0; i < locations.length; i++) {
   			this.configLocations[i] = resolvePath(locations[i]).trim();
   		}
   	}
   	else {
   		this.configLocations = null;
   	}
   }
三、 refresh函数
  @Override
  public void refresh() throws BeansException, IllegalStateException {
  	synchronized (this.startupShutdownMonitor) {
  		// 准备这个上下文以进行刷新。
  		prepareRefresh();

  		// 告诉子类刷新内部bean工厂。
  		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

  		// 准备在此上下文中使用的bean工厂。
  		prepareBeanFactory(beanFactory);

  		try {
  			// 允许在上下文子类中对bean工厂进行后处理。
  			postProcessBeanFactory(beanFactory);

  			// 调用上下文中注册为bean的工厂处理器。
  			invokeBeanFactoryPostProcessors(beanFactory);

  			// 注册拦截bean创建的bean处理器。
  			registerBeanPostProcessors(beanFactory);

  			// 为此上下文初始化消息源。
  			initMessageSource();

  			// 为此上下文初始化事件多播器。
  			initApplicationEventMulticaster();

  			// 初始化特定上下文子类中的其他特殊bean
  			onRefresh();

  			// 检查侦听器bean并注册它们。
  			registerListeners();

  			// 实例化所有剩余的(非延迟-init)单例。
  			finishBeanFactoryInitialization(beanFactory);

  			// 最后一步:发布相应的事件。
  			finishRefresh();
  		} 	catch (BeansException ex) {
  			if (logger.isWarnEnabled()) {
  				logger.warn("Exception encountered during context initialization - " +
  						"cancelling refresh attempt: " + ex);
  			}

  			// 销毁已经创建的单例,以避免悬空资源。
  			destroyBeans();

  			// 重置“活跃”的标示。
  			cancelRefresh(ex);

  			// 将异常传播给调用者
  			throw ex;
  		} finally {
  			// 重置Spring核心中的普通自省缓存,因为我们
  			// 可能再也不需要单例bean的元数据了……
  			resetCommonCaches();
  		}
  	}
  }
  • 在此方法内,我将每一步的主要功能进行了注视,总体流程也比较清晰。核心就是obtainFreshBeanFactory方法来创建新的BeanFactory。
详细看一下 obtainFreshBeanFactory方法
  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  	refreshBeanFactory();
  	return getBeanFactory();
  }

AbstractRefreshableApplicationContext.refreshBeanFactory()
  /**
   * This implementation performs an actual refresh of this context's underlying
   * bean factory, shutting down the previous bean factory (if any) and
   * initializing a fresh bean factory for the next phase of the context's lifecycle.
   * 此实现执行上下文底层的实际刷新
   *  bean工厂,关闭前一个bean工厂(如果有的话)和
   * 为上下文生命周期的下一阶段初始化一个新鲜的bean工厂。
   */
  @Override
  protected final void refreshBeanFactory() throws BeansException {
  	if (hasBeanFactory()) {
  		destroyBeans();
  		closeBeanFactory();
  	}
  	try {
  		// 创建新的beanFactory
  		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);
  	}
  }  
  • 终于到关键地方了 ,主要逻辑:
    1、 在此方法内首先回进行销毁bean 并清理BeanFactory
    2、然后创建新的BeanFactory加载beans
AbstractXmlApplicationContext.loadBeanDefinitions()
  /**
   * Load the bean definitions with the given XmlBeanDefinitionReader.
   * <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
   * method; hence this method is just supposed to load and/or register bean definitions.
   * 
   * 使用给定的XmlBeanDefinitionReader加载bean定义。
  *  bean工厂的生命周期由{@link #refreshBeanFactory}处理
  * 方法;因此,该方法仅用于加载和/或注册bean定义。
  * 
   * @param reader the XmlBeanDefinitionReader to use
   * @throws BeansException in case of bean registration errors
   * @throws IOException if the required XML document isn't found
   * @see #refreshBeanFactory
   * @see #getConfigLocations
   * @see #getResources
   * @see #getResourcePatternResolver
   */
  protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  	Resource[] configResources = getConfigResources();
  	if (configResources != null) {
  		reader.loadBeanDefinitions(configResources);
  	}
  	String[] configLocations = getConfigLocations();
  	if (configLocations != null) {
  		reader.loadBeanDefinitions(configLocations);
  	}
  }
loadBeanDefinitions函数执行时序图

在这里插入图片描述

  • 从上面的图中我们尝试梳理整个的处理过程:
  1. 封装资源文件 。当进入 XmlBeanDefinitionReader 后首先对参数 Resource 使用 EncodedResource类进行封装。
  2. 获取输入流 。 从 Resource 巾获取对应的 InputStrearn 并构造 lnputSource。
  3. 通过构造的 lnputSource 实例和 Resource 实例继续调用函数 doLoadBeanDefinitions。
    我们来看一下 loadBeanDefinitions 函数具体的实现过程 。
XmlBeanDefinitionReader.loadBeanDefinitions()
	/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
  • 整理数据准备阶段的逻辑,首先对传入的 resource 参数做封装,目的是考虑到
    Resource 可能存在编码要求的情况,其次,通过 SAX 读取 XML 文件的方式来准备 lnputSource
    对象,最后将准备的数据通过参数传入真正 的核心处理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
XmlBeanDefinitionReader.doLoadBeanDefinitions()
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

  • 在此方法内,抛除异常处理之外, 其实只做了三件事,这三件事的每一件都 必不可少 。
    1、 获取对 XML 文件的验证模式 。
    2、加载 XML 文件,并得到对应的 Document。
    3、根据返回的 Document 注册 Bean 信息 。

到这三步骤,逻辑非常复杂,特别是第三部对配置文件的解析,一时半会也实在是研究不透… 我们第一篇文稿先到这儿,接下来还是要消化一下…

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值