Spring解析Xml注册Bean流程代码跟踪

有道无术,术可求;

有术无道,止于术;

读源码是一个很枯燥的过程,但是Spring源码里面有很多值得学习的地方

加油~!!!!!

前言

使用SpringMVC的时候,通常使用下面这行代码来加载Spring的配置文件

ApplicationContext application = new ClassPathXmlApplicationContext("webmvc.xml"),那么这行代码到底进行了怎么的操作,接下来就一探究境,看看是如何加载配置文件的

Spring的配置文件

这个配置文件对于已经学会使用SpringMVC的你来说已经再熟悉不过了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <bean id="study" class="com.xiaobai.Student">
    </bean>
</beans>

那么Spring是如何进行Bean的注册的呢?经过这几天的源码查看我写下了这篇文章来作为笔记,

因为我刚开始看Spring的源码,里面有些内容可能理解的不是很到位,有错误请指出

源码查看

再此之前我先bb几句,为了方便查看源码,可以去GitHub上下载Spring的源码导入到Idea或者是eclipse中这样查看起来更方便些,同时还可以在上面写一些注释

既然使用的是ClassPathXmlApplicationContext("webmvc.xml")那就找到这个类的单参构造器查看跟踪下源码

/**
	 * Create a new ClassPathXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation resource location
	 * @throws BeansException if context creation failed
	 * 这个是创建 了 一个 ClassPathXmlApplicationContext,用来从给的XMl文件中加载规定
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

这里调用的是本类中的另外一个三个参数的构造方法,便进入到了下面这些代码中

/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
    	//设置配置文件的路径
		setConfigLocations(configLocations);
		if (refresh) {
      	   //重要的方法,需要进入查看
			refresh();
		}
	}

这里来说下这个方法的参数的意思:

  • configLocations:这个里面保存的是配置文件的路径
  • Refresh:是否自动刷新上下文
  • parent:父上下文

设置资源加载器

要跟踪下super(parent)这行代码,在它的父类中(AbstractApplicationContext类里面),有下面的代码,这段代码的作 用是获取一个SpringResource的加载器用来加载资源文件(这里你可以理解为是为了加载webmvc.xml配置文件做前期的准备)

protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}
//下面的方法在PathMatchingResourcePatternResolver类中,为了查看方便我将这两个方法写在了一起
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
	Assert.notNull(resourceLoader, "ResourceLoader must not be null");
	this.resourceLoader = resourceLoader;
}

在PathMatchingResourcePatternResolver构造方法中就设置了一个资源加载器

设置Bean信息位置

这个里面有一个setConfigLocations方法,这个里面会设置Bean配置信息的位置,这个方法的所在的类是AbstractRefreshableConfigApplicationContext,它和CLassPathXmlApplicationContext之间是继承的关系

@Nullable
private String[] configLocations;
public void setConfigLocations(@Nullable String... locations) {
		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;
		}
	}

这里面的configLocations的是一个数组,setConfigLocations方法的参数是一个可变参数,这个方法的作用是将多个路径放到configLocations数组中

阅读refresh

这个方法可以说是一个非常重要的一个方法,这在个方法里面规定了容器的启动流程,具体的逻辑通过ConfigurableApplicationContext接口的子类实现

@Override
	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 {
				//这里面的代码我删除掉了,因为我们本文是看的解析XML创建 Bean的文章,这里的代码暂时用不到,我就删除了,要不然代码太多了
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

Bean的配置文件是在这个方法里面的refreshBeanFactory方法来处理的,这个方法是在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);
  }
}

这里有一个方法是loadBeanDefinitions(beanFactory)在这个方法里面就开始解析配置文件了,进入这个方法

@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);
  //Bean读取器实现加载的方法
  loadBeanDefinitions(beanDefinitionReader);
}

进入到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

XML Bean读取器加载Bean配置资源

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
 //获娶Bean配置资源的位置
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
    reader.loadBeanDefinitions(configResources);
  }
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
    reader.loadBeanDefinitions(configLocations);
  }
}

但是本文的教程是通过ClassPathXmlApplicationContext来举的例子,getConfigResources()方法返回的是空的,就执行下面的分支

说点和本文有关也有可能没有关系的话

当代码看到这里,学习过设计模式的同鞋可能会发现我们看过的这些代码里也涉及到了委派模式策略模式因为Spring框架中使用到了很多的设计模式,所以说在看一些框架源码的时候,我们尽可能的先学习下设计模式,不管是对于看源码来说或者是对于在公司中工作都是启到了很重要的作用,在工作中使用了设计模式对于以后系统的扩展或者是维护来说都是比较方便的。当然学习设计模式也是没有那么的简单,或许你看了关于设计模式的视频或者是一些书籍,但是在工作中如果是想很好的运用出来,还是要写很多的代码和常用设计模式的。

学习设计模式也是投入精力的,Scott Mayer在《Effective C++》也说过:C++新手和老手的区别就是前者手背上有很多的伤疤。

回到正文

这个方法是在AbstractBeanDefinitionReader类中的方法,里面的代码我会做适当的删减

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		//resourceLoader的判空 处理....

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
        		 //将对应位置的配置信息解析成Resource资源
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
        		  //委派调用子类的XmlBeanDefinitionReader的方法
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			//......代码被我删除了,因为这里的代码不会说到
		}
	}

上面int loadCount = loadBeanDefinitions(resources);代码是通过委派模式来调用的XmlBeanDefinitionReader方法,那就来看下这个类中的代码

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		//....
		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();
			}
		}
		//...
	}

再来查看下doLoadBeanDefinitions方法,这个里面有具体的读取流程

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
      	   //将XML文件转换成了DOm对象
			Document doc = doLoadDocument(inputSource, resource);
      	    //此方法中有对Bean定义解析的详细过程
			return registerBeanDefinitions(doc, resource);
		}
		//...
	}

doLoadDocument方法最后调用的是DefaultDocumentLoader类中的loadDocument方法

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
 //创建文件的解析工厂
  DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
  if (logger.isDebugEnabled()) {
    logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
  }
  //创建文件的解析器
  DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
  //解析Bean配置的信息
  return builder.parse(inputSource);
}

loadDocument方法先看到这里,如果想要深入的看,自已可以跟踪下代码,接下来再看registerBeanDefinitions代码

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //拿到BeanDefinitionDocumentReader来对XML格式的配置文件进行解析
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

  int countBefore = getRegistry().getBeanDefinitionCount();
  //具体的解析过程是实现类DefaultBeanDefinititonDocumentReader完成的
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

到这里先来总结下Bean配置载入的过程:

  • 调手XML解析器将配置信息转成文档的对象
  • 在完成XML解析后,将按SPring IOC 的定义规则对文档对象解析

DefaultBeanDefinititonDocumentReader类中看下registerBeanDefinitions

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  this.readerContext = readerContext;
  logger.debug("Loading bean definitions");
  //获取到Root元素
  Element root = doc.getDocumentElement();、
   //注册Bean的定义
  doRegisterBeanDefinitions(root);   //进入方法
}
protected void doRegisterBeanDefinitions(Element root) {
  BeanDefinitionParserDelegate parent = this.delegate;
  this.delegate = createDelegate(getReaderContext(), root, parent);

  if (this.delegate.isDefaultNamespace(root)) {
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
      String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
        if (logger.isInfoEnabled()) {
          logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                      "] not matching: " + getReaderContext().getResource());
        }
        return;
      }
    }
  }

  preProcessXml(root);
  //在文档的Root元素开始对文档解析
  parseBeanDefinitions(root, this.delegate);  //进入这个方法
  postProcessXml(root);

  this.delegate = parent;
}

到了下面的这一步就是通过Bean规则解析文档元素了

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  	   //import
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
          //alias
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
         //bean
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

这里面是一堆的分支语句,对应的是解析到那一个节点,『import』『alias』,如果那个都不是将按普通的Bean来处理

这里我们重要看下processBeanDefinition方法

/**
	 * Process the given bean element, parsing the bean definition
	 * and registering it with the registry.
	 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  if (bdHolder != null) {
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    try {
      //向IOC中注册解析获得Bean的定义。Bean定义向IOC注册的入口
      // Register the final decorated instance.
      BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    }
    catch (BeanDefinitionStoreException ex) {
      getReaderContext().error("Failed to register bean definition with name '" +
                               bdHolder.getBeanName() + "'", ele, ex);
    }
    // Send registration event.
    //完成IOC注册解析获到Bean定义后,发送注册
    getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  }
}

查看parseBeanDefinitionElement代码

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		//获取bean元素中的id值
		String id = ele.getAttribute(ID_ATTRIBUTE);
		//获取bean元素中的name值
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		//获取bean元素中aliases值
		List<String> aliases = new ArrayList<>();
		//将Bean元素中的name属值存放到别名中
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		String beanName = id;
		//如果bean元素中没有配置id属性,将别名中的第一个值给beanName
		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) {
						//如果bean元素中没有配置id 别名 name 没有子元素 则为解析的bean生成一个唯一的beanName注册到容器
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						//如果bean元素中没有配置id 别名 name 有子元素 则为解析的bean生成一个唯一的beanName注册到容器
						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;
	}

上面的流程完成后,我们再回到了DefaultBeanDefinitionDocumnetReader类中的processBeanDefinition方法

在这个方法里面我们在查看registerBeanDefinition方法

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		//向IOC容器中注册definitionHolder   在注册的时候,真正的完成注册功能是DefaultListableBeanFactory
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());  //进入这个方法

		// Register aliases for bean name, if any.
		// 如果在解析的过程中拿到了别名,则向IOC注册别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

查看registerBeanDefinition方法,这个方法在DefaultListableBeanFactory类中。

//在这个Map里面保存了Bean定义对象的映射
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  throws BeanDefinitionStoreException {

  //....
  if (beanDefinition instanceof AbstractBeanDefinition) {
    try {
      ((AbstractBeanDefinition) beanDefinition).validate();
    }
    catch (BeanDefinitionValidationException ex) {
      throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                                             "Validation of bean definition failed", ex);
    }
  }

  BeanDefinition oldBeanDefinition;

  oldBeanDefinition = this.beanDefinitionMap.get(beanName);
  if (oldBeanDefinition != null) {
    if (!isAllowBeanDefinitionOverriding()) {
      throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                                             "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                                             "': There is already [" + oldBeanDefinition + "] bound.");
    }
    else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
      // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
      if (this.logger.isWarnEnabled()) {
        this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                         "' with a framework-generated bean definition: replacing [" +
                         oldBeanDefinition + "] with [" + beanDefinition + "]");
      }
    }
    else if (!beanDefinition.equals(oldBeanDefinition)) {
      if (this.logger.isInfoEnabled()) {
        this.logger.info("Overriding bean definition for bean '" + beanName +
                         "' with a different definition: replacing [" + oldBeanDefinition +
                         "] with [" + beanDefinition + "]");
      }
    }
    else {
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Overriding bean definition for bean '" + beanName +
                          "' with an equivalent definition: replacing [" + oldBeanDefinition +
                          "] with [" + beanDefinition + "]");
      }
    }
    this.beanDefinitionMap.put(beanName, beanDefinition);
  }
  else {
    if (hasBeanCreationStarted()) {
      // 在这里对启动的信息进行锁定,不可以在修改,保证数据一致
      //这里是注册的过程
      synchronized (this.beanDefinitionMap) {
        this.beanDefinitionMap.put(beanName, beanDefinition);
        List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
        updatedDefinitions.addAll(this.beanDefinitionNames);
        updatedDefinitions.add(beanName);
        this.beanDefinitionNames = updatedDefinitions;
        if (this.manualSingletonNames.contains(beanName)) {
          Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
          updatedSingletons.remove(beanName);
          this.manualSingletonNames = updatedSingletons;
        }
      }
    }
    else {
      // Still in startup registration phase
      this.beanDefinitionMap.put(beanName, beanDefinition);
      this.beanDefinitionNames.add(beanName);
      this.manualSingletonNames.remove(beanName);
    }
    this.frozenBeanDefinitionNames = null;
  }
  //检查是否已经注册过相同的BeanDefinition
  if (oldBeanDefinition != null || containsSingleton(beanName)) {
    resetBeanDefinition(beanName);
  }
}

到这里就完成了IOC容器初始化的工作。

在注册的流程里面关于子元素(property list)加载没有写到,会在另一篇文章中写到

原文地址:https://www.cnblogs.com/sdayup/p/13236726.html