三、Spring怎样解析XML并注册BeanDefinition

一、概述

1. 为什么需要理解XML配置解析?

我是一个刚交了一年社保的一年工作经验的小老弟,从大学刚接触软件开发到毕业正式入职所接触到JavaSE或JavaEE项目中,基本都会使用Spring作为项目的对象管理容器。尤其在大学期间,WEB项目使用Spring的时候基本都是通过配置applicationContext.xml全局配置文件,然后使用web.xml配置ContextLoaderListener加载这个全局配置文件去初始化容器。
早期版本的Spring,只能通过加载XML去启动配置文件,演变到现在也可以通过扫描类和注解去启动Spring容器,但Spring的本质和核心是不会变的。通过学习XML配置解析可以起到窥一斑而知全貌的效果,当你在研究扫描类和注解的时候会突然发现,大致的流程基本是很相似的。

2. BeanDefinition的注册有必要了解吗?

当你成功启动了一个Spring容器,有一个Bean配置的是懒加载,那么此时这个Bean在容器中是否存在?或者是以什么方式存在?
答案就是在一个需要被实例化的类还没有创建的时候,它在Spring中就是一个BeanDefinition,也就是这个Bean的定义,BeanDefinition包含了这个对象实例化所需要用到的所有信息。(详情可见之前的文章有描述过)
由此可见BeanDefinition注册是Spring容器的启动中非常重要的一环,BeanDefinition的注册不难,简单来讲就是Spring中有一个装BeanDefiniton的Map,当你扫描解析完XML的Bean标签之后就会注册到这个Map中。

二、ClassPathXmlApplicationContext解析XML

1. ClassPathXmlApplicationContext的解析XML入口方法

    // 方法在AbstractApplicationContext的709行左右
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		/**
		 * 主要内容:配置信息到beanDefinition的转换过程
		 * 模版设计模式
		 * Spring中运用的最多的设计模式
		 * 		obtainFreshBeanFactory方法是一个模版方法
		 * 		refreshBeanFactory方法是一个钩子方法,需要子类去实现
		 */
		refreshBeanFactory();

		return getBeanFactory();
	}

这个方法在容器的refresh()流程中被调用,ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 实际调用的是ClassPathXmlApplicationContext的父类AbstractApplicationContext的方法。(模版方法模式)
obtainFreshBeanFactory()方法也是一个模版方法,其中定义了一个钩子方法refreshBeanFactory(),refreshBeanFactory()方法在AbstractApplicationContext中是一个抽象方法,需要子类去实现。

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

1.1 XML解析前创建工厂

经过查询容器类的继承结构,发现最终调用的是AbstractRefreshableApplicationContext的refreshBeanFactory()方法。该方法中不重要的掐头去尾已经省略了,主要有记个关键点和概念需要理解一下,就是BeanFactory是一个实例工厂,主要是用来管理Bean的,容器Context包含了BeanFactory。

	protected final void refreshBeanFactory() throws BeansException {
		// ...
		try {
			/**
			 * 创建beanFactory
			 * beanFactory:实例工厂,无论什么实例只要被spring管理,都在这个工厂里面,主要是bean的相关操作。
			 * Context和beanFactory的关系:从属关系。  不重要理解即可
			 */
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			/*
			 * 容器重要属性设置: 
			 * 	1.是否允许同名bean
			 * 	2.是否允许循环依赖
			 */
			customizeBeanFactory(beanFactory);
			/**
			 * 	解析XML,注册beanDefinition 重要:* * * * *
 			 */
			loadBeanDefinitions(beanFactory);
			// ...
		}
		// ...
	}

其中我们创建的工厂就是Spring默认的工厂,DefaultListableBeanFactory。

        protected DefaultListableBeanFactory createBeanFactory() {
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}

1.2 创建工厂后设置一些参数

在上述方法流程中会执行customizeBeanFactory(),这个方法会设置一些工厂的属性,是否允许同名bean存在和是否允许循环依赖。(看代码注释即可)

	protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		/*
		 * 是否允许同名的bean存在
		 * 默认是不允许的
		 */
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		/*
		 * 是否允许循环依赖
		 * 默认是允许的
		 */
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}

1.3 执行加载XML注册BeanDefinition的方法。

同样是在1.1的方法流程中,执行了loadBeanDefinitions(beanFactory);方法并传入当前创建的BeanFactory。
loadBeanDefinition(DefaultListableBeanFactory beanFactory)这个方法在AbstractRefreshableApplicationContext是一个抽象方法,也需要子类去实现它。经过查询其继承结构发现是AbstractXmlApplicationContext实现的。

	protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws BeansException, IOException;

在AbstractXmlApplicationContext抽象类,loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法主要是采用委托设计模式去实现的。 也就是说容器将XML的解析委托给了XmlBeanDefinitionReader类的实例,自身并没有详细的解析流程,将自身作为参数传入构造器,使得XmlBeanDefinitionReader类的实例可以再解析XML的过程中在工厂中注册BeanDefinition。

     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		/**
		 * 创建XML解析器,这里涉及到了委托设计模式
		 * 委托设计模式:【专人专事】
		 * 		主类要持有委托类的引用,在实现服务时把具体实现的逻辑委托给委托类去实现。
		 * 创建XML解析器
		 * Create a new XmlBeanDefinitionReader for the given BeanFactory.
		 */
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		/**
		 * 使得XmlBeanDefinitionReader持有当前Spring容器的对象,这个很重要需要记住。
		 * 把当前的Spring容器当作ResourceLoader注入到XmlBeanDefinitionReader
		 * ClasspathXmlApplicationContext 类的继承链最顶端是一个 ResourceLoader
		 */
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		initBeanDefinitionReader(beanDefinitionReader);
		/**
		 * 执行解析,解析XML为BeanDefinition过程很重要
		 */
		loadBeanDefinitions(beanDefinitionReader);
	}
       // XmlBeanDefinitionReader 重载的 loadBeanDefinitions 方法,拿URL,直接委托。
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		// ...
		//没有多余的代码简单的条件直接委托给其他对象去执行,就是专人专事。
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
                 // 走这个方法  重要 : * * * * *
			reader.loadBeanDefinitions(configLocations);
		}
	}

2. 解析XML从AbstractBeanDefinitionReader开始

2.1 解析URL的心路历程

紧接上文执行reader.loadBeanDefinitions(configLocations);,实际调用的是AbstractBeanDefinitionReader类的loadBeanDefinitions(String… locations) 方法。loadBeanDefinitions(String… locations)方法又会循环遍历这个locations去挨个解析location。

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int count = 0;
		/**
		 * 循环遍历解析各个路径下的xml文件
		 */
		for (String location : locations) {
			count += loadBeanDefinitions(location);
		}
		return count;
	}

loadBeanDefinitions(String location)方法会调用另一个重载的方法,loadBeanDefinitions(String location, @Nullable Set actualResources)方法,只不过actualResources参数传的是null。

	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		/**
		 * 根据地址解析XML文件
		 */
		return loadBeanDefinitions(location, null);
	}

2.2 从解析URL到解析Resource的心路历程

下面代码先执行了 getResourceLoader() ,这个ResourceLoader就是容器本身,在创建XmlBeanDefinitionReader时候持有的容器对象。因为容器本身顶层实现了ResourcePatternResolver接口,所以可以通过getResources方法得到资源数组(Resource[])。
然后再次调用重载的遍历解析资源的loadBeanDefinitions(Resource… resources)方法。

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		/**
		 * resourceLoader在loadBeanDefinitions前被注入到reader对象中
		 * 这个resourceLoader其实就是容器本身对象
		 */
		ResourceLoader resourceLoader = getResourceLoader();
		// .......
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				/**
				 * Spring.xml对象封装成一个Resource对象,Resource对象中有文件对象,文件流。
				 */
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				/**
				 * 解析这些Resource对象
				 */
				int count = loadBeanDefinitions(resources);
			}
		// .......
	}

转来转去,还没有结束,批量解析资源的loadBeanDefinitions遍历调用解析单个资源的loadBeanDefinitions方法。

	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int count = 0;
		for (Resource resource : resources) {
			/**
			 * AbstractBeanDefinitionReader父类的方法
			 * 模版方法
			 */
			count += loadBeanDefinitions(resource);
		}
		return count;
	}

2.3 从解析Resource到解析InputStream的心路历程

上面遍历调用解析Resource的是,调用的是BeanDefinitionReader接口的这个方法

	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

实际执行的是XmlBeanDefinitionReader实现类的方法,然后又会将这个resource编码后再次调用重载的方法。

	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException 
       {
		/**
		 * 实现类实现的钩子方法
		 * resource -> Encoded 将流变成有编码的流
		 */
		return loadBeanDefinitions(new EncodedResource(resource));
	}

我们来查看下加载有编码的流的方法是什么样子的。就是获取编码流中的InputStream然后继续去解析这个流。

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		//......
		try {
			/**
			 * 拿到文件流
			 */
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				/*
				 * InputSource是XML文档解析的类 , JDK的类。专门做XML文档解析的一个类
				 */
				InputSource inputSource = new InputSource(inputStream);
				//......
				/**
				 * 加载Bean的定义信息
				 */
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
		//......
	}

2.4 Java 的 SAX 解析XML

承接2.3继续到了doLoadBeanDefinitions(InputSource inputSource, Resource resource)看见了曙光,终于是将inputSource和resource解析成了Document对象。提到Document对象我们都不会陌生,就是XML解析出的文档信息,里面包含了Node标签,也就是我们配置的Bean。
然后就是注册BeanDefinition的方法,在了解BeanDefinition注册之前,需要详细的了解一下doLoadDocument(InputSource inputSource, Resource resource) 这个方法的具体实现。

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
                //...........
			/**
			 * xml文件流对象解析为document对象
			 */
			Document doc = doLoadDocument(inputSource, resource);
			/**
			 * 将document对象解析为BeanDefinition,并返回解析的数量
			 */
			int count = registerBeanDefinitions(doc, resource);
                //...........
    }

这个方法的实现很长一串,我们点进loadDocument( … … )这个方法。

	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		/**
		 * 实际解析XML InputSource的方法,解析成一个Document对象
		 */
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

可以看着这是一个SAX解析的常规流程,创建工厂,创建builder,解析输入流,然后返回Document。

	@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		/**
		 * XML解析封装document对象常规流程 SAX解析常用
		 * 1.创建工厂
		 * 2.创建builder
		 * 3.解析输入流
		 */
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		/**
		 * 解析成Document对象并返回
		 */
		return builder.parse(inputSource);
	}

到此为止将一个XML解析成Document的内容就结束了,当然Document还需要进一步解析成一个个Element并提取每个元素的信息如果是Bean的话就组册BeanDefinition。

3. 解析Document,注册BeanDefinition

3.1 开始解析Document

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
                //...........
			/**
			 * xml文件流对象解析为document对象
			 */
			Document doc = doLoadDocument(inputSource, resource);
			/**
			 * 将document对象解析为BeanDefinition,并返回解析的数量
			 */
			int count = registerBeanDefinitions(doc, resource);
                //...........
    }

回到doLoadBeanDefinitions这个方法,执行完将Resource转换为Document后,着重看解析document并注册BeanDefinition的内容。下面我们看registerBeanDefinitions(doc, resource)方法具体实现。

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		/**
		 * 委托设计模式:
		 * 把解析的工作委托给BeanDefinitionDocumentReader对象
		 * 已把xml -> document ,继续委托给DocumentReader解析document
		 */
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();

		/**
		 * 解析Document对象并注册BeanDefiniton
		 * 方法:registerBeanDefinitions(args1,args2)
		 * 参数:Document doc, XmlReaderContext readerContext
		 */
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

可以看到我们解析Document对象依旧是吧这个解析的过程委托给了BeanDefinitionDocumentReader类的实例。
上述执行流程中关注下 createReaderContext(resource) ,这个方法是返回XmlReaderContext类的实例,并且在构造实例的过程中,持有了当前的XmlBeanDefinitionReader的对象。之前我们提到过XmlBeanDefinitionReader持有了当前容器的引用,所以在createReaderContext(resource)方法的返回值XmlReaderContext实例中也间接持有了当前容器的引用,它可以去注册BeanDefinition。
继续看documentReader.registerBeanDefinitions这个调用方法的具体实现。

public interface BeanDefinitionDocumentReader {
	void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
			throws BeanDefinitionStoreException;

}

来到了BeanDefinitionDocumentReader接口,最终调用的是下面的DefaultBeanDefinitionDocumentReader实现类的方法。可以看到方法将readerContext的实例持有到自身的对象之中。并开始解析根结点。

	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		/**
		 * 把Document根结点 (root) 传进去
		 */
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

3.2 具体Element标签的解析

来到根节点Element的具体解析实现,其实可以忽略大部分的内容,我们只需要看parseBeanDefinitions(root, this.delegate)这个主要的解析标签方法就好了,因为获取delegate,这个delegate是用来解析自定义标签的,自定义标签解析内容很多,所以需要总结一篇详细的文章来描述自定义标签的解析,这里就暂时忽略了。

	protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		/**
		 * 主要是获取delegate,用来委托给第三方解析起解析自定义标签
		 */
		this.delegate = createDelegate(getReaderContext(), root, parent);
		if (this.delegate.isDefaultNamespace(root)) {
			//  ...... 省略多行代码
		}
		/**
		 * 预处理模版方法 暂时无具体实现
		 */
		preProcessXml(root);
		/**
		 * 主要看这个方法,标签的具体解析过程
		 */
		parseBeanDefinitions(root, this.delegate);
		/**
		 * 后处理模版方法 暂时无具体实现
 		 */
		postProcessXml(root);
		this.delegate = parent;
	}

这里他首先会获取根结点中的所有子结点,也就是我们配置的Bean,Import等传统标签,当然也有context-componentsacn等自定义的标签。这里我们主要看默认的传统标签解析。自定义标签解析需要详细的总结一篇文章来描述。

	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解析
						 */
						delegate.parseCustomElement(ele);
					}
				}
			}
		}else {delegate.parseCustomElement(root);}
	}

默认标签中我们常用的也就是 标签,最重要的是标签,这个是我们最最常用的。我给标了重要程度五颗星。进入processBeanDefinition(ele, delegate);这个方法详细的看下。

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			/**
			 * import标签的解析,重要程度:*
			 */
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			/**
			 * alias标签的解析 别名标签,重要程度:*
			 */
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			/**
			 * bean标签的解析,重要程度:* * * * *
			 */
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			/**
			 * recurse ,不重要 外层的beans
			 */
			doRegisterBeanDefinitions(ele);
		}
	}

到这里我们看其实我之前看源码的注释描述的还是很多的,可见这里面涉及到了很多知识点,但是不重要,我们只关注 Element 具体是如何解析的,并且这个解析好的BeanDefinitionHolder注册到了容器的哪个地方,有没有什么业务规则。

3.2.1 大致描述processBeanDefinition方法的实现:
  1. 将element解析成BeanDefinitionHolder
  2. 装饰这个BeanDefinitionHolder如果需要的话
  3. 注册这个BeanDefinitionHolder
    接下来会把这三点拆分成3个小的点来描述。
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		/**
		 * 方法:parseBeanDefinitionElement
		 * 解析document封装成beanDefinition
		 *
		 * 重要程度:* * * * *
		 */
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			/**
			 * 没吊用,但需要学习设计思想。
			 *
			 * 装饰者设计模式,加上SPI(service provider interface)的设计思想SPI(Mybatis,Spring,Dubbo的SPI扩展)
			 * SPI用来解耦,扩展的设计思想,在不改变原有代码的前提下进行开发,实现热插拔。和策略模式有点像
			 * SPI简单来讲就是加载配置文件通过配置文件类中的类路径信息,再不修改核心代码的前提下扩展功能。
			 *
			 * 内容:
			 * 1.namespace uri和解析类建立映射关系
			 * 2.解析类实现统一接口完成多态
			 * 3.beandefinition的不断包装/装饰
			 *
			 * 不使用构造器和属性注入,在bean标签中使用前缀属性 p: c:进行注入 ,在xmlns中加入schema/p schema/c 约束
			 *
			 * 重要程度:*
			 */
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

			try {

				/**
				 * 逻辑很简单,构建 别名 -> beanname -> beandefinition 的映射。
				 *
				 * 完成document到BeanDefinition对象的转换,对BeandDefinition对象进行缓存注册
				 * 重要程度: * * *
				 */
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

3.3 标签元素解析成BeanDefinitionHolder的过程

先进入 delegate.parseBeanDefinitionElement(ele);的方法看一下,依然是重载外面包个壳。

	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
		return parseBeanDefinitionElement(ele, null);
	}

再次进入parseBeanDefinitionElement(ele, null);这个方法。

3.3.1 parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 外层提取信息具体实现
	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		// 解析过程不复杂,但是解析项比较多。BeanDifinition的属性比较多。
		/**
		 * 参数提取:提取标签的 ID 和 name
		 */
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		/**
		 * 标签name  = ","或";"分割的字符串 解析别名列表
		 */
		List<String> aliases = new ArrayList<>();
		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.isTraceEnabled()) {
				logger.trace("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}
		/**
		 * 检查beanname的唯一性
		 */
		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}
		/**
		 * 解析这个元素 ele 剩余的全部的属性
		 * 返回 名为beanName的 AbstractBeanDefinition 对象
		 * 详细的解析过程:* * * * *
		 */
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			// ............. 省略了一大堆代码
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			/**
			 * BeanDefinition再次进行一个包装 -> BeanDefinitionHolder
			 *
			 * BeanDefinitionHolder是BeanDefinition,名称,别名的组装
			 */
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}

代码概述:

  1. 先提取了标签的id和name,如果name的配置了多个别名还要拆一下,如果id是空的直接拿第一个别名用一下。
  2. 因为当前参数containingBean是空的,所以要检测一下Bean的唯一性,也就是beanName和别名的唯一性。方法里面内容不多很好理解,自己点进去看下就好了。
  3. parseBeanDefinitionElement方法是,具体的提取信息创建BeanDefinition,提取属性设置属性。(这个方法最重要)
  4. 包装成BeanDefinitionHolder返回,BeanDefinitionHolder和BeanDefinition区别就是BeanDefinitionHolder包装了除了类定义外的beanName和别名数组。
3.3.2 parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) 属性设置具体实现

进入 parseBeanDefinitionElement(ele, beanName, containingBean); 方法查看具体实现。

@Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {
		this.parseState.push(new BeanEntry(beanName));
		/*
		 * 获取它的class属性,如果有。
		 */
		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		/*
		 * 获取它的parent属性,如果有。
		 */
		String parent = null;
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}
		try {
			/**
			 * 创建一个GenericBeanDefinition对象并设置父对象id
			 */
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
			/**
			 * 重要: * * * * *
			 * 解析bean标签的属性把属性设置到对象中
			 */
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

			/*
			 * Bean标签的Meta子标签的解析,没什么用
			 */
			parseMetaElements(ele, bd);
			/**
			 * lookup-method  demo05
			 * 替代某方法的返回值。 使用代理实现。
			 * 重要程度:* *
			 */
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
			/**
			 * replace-method demo06
			 * arg-type子标签区分重载参数类型
			 * 在不改变原有代码的基础上进行增强,可以用AOP替代.
			 * 重要程度:* *
			 */
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
			/**
			 * 解析Bean中的constructor-arg标签
			 * 重要程度:* *
 			 */
			parseConstructorArgElements(ele, bd);
			/**
			 * 解析Bean中的property, 属性注入可以用@Value替代
			 * 重要程度:* *
			 */
			parsePropertyElements(ele, bd);
			/**
			 * @Qualifier指定注入哪个bean
			 * 重要程度:* *
			 */
			parseQualifierElements(ele, bd);
			// .............省略
			/**
			 * 整个bean标签就解析完了
			 */
			return bd;
		}
		// .............省略
	}

具体实现内容概述:

  1. 获取标签中的class属性
  2. 获取标签中的parent属性
  3. 基于class和parent先创建一个BeanDefinition对象
  4. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);这个方法里面会对BeanDefinition设置很多属性,如scop属性、abstract属性、lazy属性、autowireMode属性、depends-on属性、autowire-candidate属性、init-method属性、destory-method属性、factory-bean和factory-method属性等。如果对于属性含义不了解看下我之前等 《快速理解Spring加载流程》的那个文章,里面有对BeanDefinition的属性的一些描述。
  5. 子标签解析,解析后的键值对会存储到BeanDefinition的attributes这个Map中。如果需要用到这个值,需要先获取BeanDefinition。
  6. lookup-method这个属性的作用是,如果创建当前这个类的Bean,会给这个类的某一个方法塞一个返回值。如下案例就是调用ShowSixClass类的getPeople方法,我返回woman。(就是这么个作用,实际信息存储在BeanDefinition的MethodOverrides属性中)
    <bean id="people" class="com.jd.nlp.dev.muzi.spring5.exercise.demo05.ShowSixClass" >
        <!--简单理解就是 给某个方法塞返回值 体现出一种多态的方式-->
        <lookup-method name="getPeople" bean="woman" />
    </bean>
  1. replace-method这个属性就是增强某个方法,这个可以使用AOP去代替,但是还是弄个案例看一下。这个方法需要进行业务功能增强,但是又不希望在原来基础上修改,可以用 replaced-method标签。下述案例就是originClass这个Bean在调用method(String str)方法的时候会调用replaceClass的reimplement方法。(实际信息存储在BeanDefinition的MethodOverrides属性中)
//-----------配置--------------
    <bean id="replaceClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.ReplaceClass" />

    <bean id="originClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.OriginClass">
        <replaced-method name="method" replacer="replaceClass">
            <!-- 使用arg-type来区分重载的方法 -->
            <arg-type match="java.lang.String" />
        </replaced-method>
    </bean>
// ------------代码---------------
public class OriginClass {
    public void method(String param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
    public void method(List param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
}
public class ReplaceClass implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("I am replace method -> reimplement -> begin");
        System.out.println("obj:"+ obj.toString());
        System.out.println("method:" + method.getName());
        System.out.println("args:" + args);
        System.out.println("I am replace method -> reimplement -> end");
        return null;
    }
}
  1. constructor-arg标签的内容最终会存储在 BeanDefinition的 ConstructorArgumentValues 属性中,用作实例化有参构造函数参数获取。
  2. 解析Bean中的property,在当前支持注解扫描的版本下,可以用@Value替代 。
  3. @Qualifier指定注入哪个bean

以上就是parseBeanDefinitionElement(ele, beanName, containingBean)方法的具体实现内容,其实梳理下来就很简单,无非就是提取标签中配置的参数嘛,然后都包装到BeanDefinition对应的属性当中,最终把这个BeanDefinition返回。
至此,一个Bean标签的基本信息就被解析完了。返回BeanDefinition并被包装成一个Holder对象,然后我们回到之前解析的主流程中。

3.4 BeanDefinitionHolder是否需要被装饰?

再次看一下解析一个传统 标签主流程的代码。

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		/**
		 * 方法:parseBeanDefinitionElement
		 * 解析document封装成beanDefinition
		 * 重要程度:* * * * *
		 */
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			/**
			 * 没吊用,但需要学习设计思想。
			 * 装饰者设计模式,加上SPI(service provider interface)的设计思想SPI(Mybatis,Spring,Dubbo的SPI扩展)
			 * SPI用来解耦,扩展的设计思想,在不改变原有代码的前提下进行开发,实现热插拔。和策略模式有点像
			 * SPI简单来讲就是加载配置文件通过配置文件类中的类路径信息,再不修改核心代码的前提下扩展功能。
			 * 内容:
			 * 1.namespace uri和解析类建立映射关系
			 * 2.解析类实现统一接口完成多态
			 * 3.beandefinition的不断包装/装饰
			 * 不使用构造器和属性注入,在bean标签中使用前缀属性 p: c:进行注入 ,在xmlns中加入schema/p schema/c 约束
			 * 重要程度:*
			 */
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			// ................................................
		}
	}

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);装饰这个BeanDefinitionHolder如果需要的话?

3.4.1 什么是装饰?

在我理解,装饰就是不断的对一个对象进行包装,填充这个对象的可变的属性列表值,使得我们可以动态的去达到自己想要表达的内容。
有的装饰是通过继承不断的去修改最初始的内容,不断的扩充自己所拥有的东西。而在Spring的装饰中,其实就是通过 SPI(service provider interface)服务发现思想,去判断我这个Bean是否需要被装饰。

3.4.2 什么是SPI?

SPI是service provider interface的缩写,是一种服务发现机制。

Spring的SPI设计首先要理解什么是 " namespaceURI ",如下面代码所示 xmlns 后面配置的 “http://www.springframework.org/schema/beans” 就是 " namespaceURI "。xsi:schemaLocation里面配置的是你的自定义标签的具体Schema(自定义标签解析相关内容暂且不提)。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">/>
    <bean id="decoratorBean" class="com.jd.nlp.dev.muzi.spring5.exercise.demo07.DecoratorBean"
          p:username="jack" p:password="123"

          c:age="12" c:sex="1"
    />
</beans>

我们看上面配置文件中bean标签中有两种很奇怪的属性 “p:” 和 “c:” ,其中 “p:” 配置和Property功能类似,“c:” 配置和construct-args子标签属性功能类似,但是在Spring中这种属性是如何解析的呢?

想要知道如何解析“p:”和“c:”,首先要打开spring-beans的这个jar包,找到META-INF目录,找到spring.handlers文件打开。这里面定义的就是“p:”和“c:”的解析类与namespaceURI的关系。

文件:META-INF/spring.handlers

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

回到之前调用装饰方法的入口,看一下bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);具体的方法是怎么实现的?
依然是包装了一下,然后调用到实际的实现方法中,首先他是获取标签Element元素的全部Node去遍历,查看是否需要装饰。主要的方法是 decorateIfRequired 方法。

	public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
		return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
	}
	public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
		BeanDefinitionHolder finalDefinition = definitionHolder;
		/**
		 * 通过元素的属性来装饰
		 */
		NamedNodeMap attributes = ele.getAttributes();
		// 循环
		for (int i = 0; i < attributes.getLength(); i++) {
			Node node = attributes.item(i);
			// 装饰如果需要
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}
		/**
		 * 通过子标签装饰
		 */
		NodeList children = ele.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
			}
		}
		return finalDefinition;
	}

这个方法我们可以看到,它是在看当前Node是否能得到NamespaceURI,得到了NamespaceURI就会通过NamespaceURI获取NamespaceHandler。然后调用handler.decorate去装饰这个beanDefinition。

	public BeanDefinitionHolder decorateIfRequired(
			Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

		// node 就是 p:xxxx="xxxx"  demo07
		// 根据node获取node的命名空间,形如:http://www.springframework.org/schema/p
		String namespaceUri = getNamespaceURI(node);

		if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {

			/**
			 * SPI服务发现思想,通过URI获取spring.handlers配置的处理类
			 *
			 * resolve(namespaceUri)方法中有详细的解析过程
			 */
			NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

			if (handler != null) {
				/**
				 * 实际的解析类,调用装饰方法。 可以理解为 handler 是一个装饰者, beanDefinition是被装饰者。
				 */
				BeanDefinitionHolder decorated =
						handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
				// ................
			}
			// ................
		}
		return originalDef;
	}

看下resolve具体实现,是如何获取到NamespaceHandler的?

  1. getHandlerMappings获取到所有jar包中的spring.handlers文件中的namespaceURI和其对应的处理类的类路径,放入一个Map中。
  2. 反射这个类,并创建这个累的实例,得到namespaceHandler。
  3. 调用namespaceHandler的init方法。
  4. 把实例好的对象和namespaceURI映射上
  5. 返回这个namespaceHandler对象。

这样上文通过resolve获得的namespaceHandler对象,就是spring.handlers配置的当前namespaceURI对应的解析类的实例。这样Spring就可以通过namespaceHandler调用decorate方法进行装饰了。

	public NamespaceHandler resolve(String namespaceUri) {
		/**
		 * 加载"META-INF/spring.handlers"文件,建立URI和处理类的映射关系
		 *
		 * uri和类的映射关系,通过uri唯一找到一个类
		 *
		 * 方法:getHandlerMappings
		 * 重要程度:* * *
		 */
		Map<String, Object> handlerMappings = getHandlerMappings();

		// 根据URI就可以找到唯一的处理类(字符串)
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			// 处理类(字符串)反射
			String className = (String) handlerOrClassName;
			try {
				/**
				 * 反射这个类
				 */
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
					// ......................
				}
				/**
				 * 基于类对象来实例化
				 * 备注:所有处理类必须继承NamespaceHandler,实现多态。
				 * 例如:
				 *   SimpleConstructorNamespaceHandler implements NamespaceHandler
				 *
				 *   所有spring.handlers这些命名解析类都有一个特点是必须实现NamespaceHandler接口,来实现多态
				 */
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

				// 调用处理类初始化方法
				namespaceHandler.init();

				// 替换映射关系key对应的值
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}
		// ......................
		}
	}

简单看下"p:"属性对应的namespaceURI对应的解析类把,这是p的spring.handlers的配置。对应的解析类是SimplePropertyNamespaceHandler。

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

这是p的解析类的代码,SimplePropertyNamespaceHandler类的 init 方法没有具体的内容,但是decorate有装饰的详细逻辑,就不解读了。就是往BeanDefinition对应的属性塞值。和之前那波设置属性的操作差不多。“p:” 对应之前的逻辑的就是"property"子标签属性设置。

public class SimplePropertyNamespaceHandler implements NamespaceHandler {
	private static final String REF_SUFFIX = "-ref";
	@Override
	public void init() {
	}
	/**
	 * p:实际对应的装饰方法
	 */
	@Override
	public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
		/**
		 * 被装饰对象 definition
		 * 主要内容是 解析  p:xxxx="xxxx" 内容,封装到属性 MutablePropertyValues 列表元素中去
		 */
		if (node instanceof Attr) {
			Attr attr = (Attr) node;
			String propertyName = parserContext.getDelegate().getLocalName(attr);
			String propertyValue = attr.getValue();
			MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
			if (pvs.contains(propertyName)) {
				parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
						"both <property> and inline syntax. Only one approach may be used per property.", attr);
			}
			if (propertyName.endsWith(REF_SUFFIX)) {
				propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
				pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
			}
			else {
				/**
				 * 把属性内容加入到definition的MutablePropertyValues列表中
				 * 这样一种反复的对definition进行装饰/包装,体现了装饰者设计模式的感觉。
				 *
				 * 具体谁是装饰者已经不重要了,对beanDefinition已经进行反复的修改了。
				 */
				pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
			}
		}
		return definition;
	}

}

3.5 BeanDefinition注册的位置在哪里?

回归到之前解析标签的主流程中, BeanDefinitionReaderUtils.registerBeanDefinition这个方法就是在注册BeanDefinition。
getReaderContext() 获取到的就是之前从第一次委托对象持有的容器本身的引用,一直被间接的持有着这个对象。容器本身是含有BeanFactory和注册器的,所以可以获取到BeanDefinitionRegistry。
BeanDefinitionRegistry其实就是DefaultListableBeanFactory,BeanDefinitionRegistry是个接口。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //.........................
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            //....................
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            // ................................................
        }
    }
3.5.1 看下BeanDefinitionReaderUtils.registerBeanDefinition具体实现:(注册BeanDefinition至特定容器)
  1. BeanName和BeanDefinition注册构建映射关系
  2. Alias和BeanName注册构建映射关系

可以理解,我们可以通过BeanName快速的找到BeanDefinition,当然也可以通过别名,找到BeanName间接的找到BeanDefinition。

	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		/**
		 * BeanName和BeanDefinition注册构建映射关系
		 * 重要:* * *
		 */
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		/**
		 * Alias和BeanName注册构建映射关系
		 */
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}
3.5.2 看下registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());具体实现:(DefaultListableBeanFactory类的实现方法)
  1. 代码有点长该省略的都省略了
  2. 装beanDefinition的容器是 beanDefinitionMap
  3. 装beanName的集合是 beanDefinitionNames

以上2和3需要牢记,玩Spring源码debug的时候需要去这里找内容。

	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
		/**
		 * 重要代码最下方
		 */
		// .......................省略一万行
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			// .......................省略一万行
		}
		else {
			// .......................省略一万行
			else {
				/**
				 * 把BeanDefinition缓存到Map中
				 */
				this.beanDefinitionMap.put(beanName, beanDefinition);
				/**
				 * 把 beanname 放到 BeanDefinitionNames 这个List中,在Bean实例化时需要使用到该List。
				 * 该List包含所有的beanDefinition的名称
				 */
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}
              // .......................
	}
3.5.3 看下registry.registerAlias(beanName, alias);方法的具体实现:

GenericApplicationContext.class

	public void registerAlias(String beanName, String alias) {
		/**
		 * 构建别名和 beanName的映射关系
		 */
		this.beanFactory.registerAlias(beanName, alias);
	}

SimpleAliasRegistry.class

	public void registerAlias(String name, String alias) {
		//  ................... 
		/**
		 * 映射关系在最下面
		 */
		synchronized (this.aliasMap) {
			//  ................... 省略一万行
			else {
				String registeredName = this.aliasMap.get(alias);
				//  ................... 省略一万行

				/**
				 * 装的是别名和beanName【id的名称】的映射关系
				 * 别名取对象:
				 * 		别名 - beanName 有映射关系
				 * 	 	beanName - 	beanDefinition 有映射关系
				 *
				 * 	 总体来说如果通过别名找beanDefinition需要二级映射
				 */
				this.aliasMap.put(alias, name);
				//  ................... 
			}
		}
	}

可以看到 alias 和 beanName的映射关系是在 aliasMap 中存储的。最外层是循环别名数组,以当前item 别名为key,以beanName为value一对一对存的。所以通过任意一个定义好别名都可以找到对应的beanName。

至此,ClassPathXmlApplicationContext 的 解析XML注册BeanDefinition流程就梳理完了。梳理的很难受,模版设计模式扩展性不错,就是跳来跳去的,抓耳挠腮,看湿了 … …

三、总结

简单总结一下,虽然微服务的大环境下,ClassPathXmlApplicaitionContext容器在我们日常的代码中越来越少的去使用了,但是万变不离其宗,Spring不论再怎么演变,初始的结构就是这样,以后只能是扩展和兼容,相似的功能还会复用之前的代码。所以吃透一套流程,待我们分析注解配置启动容器的时候,也是小事一桩。

就目前来看BeanDefinition的这些属性,有一些我们是基本不会用到的,就比如lookup-method,init-method(实现InititalizeBean接口,@PostConstruct可以替代),factory-bean(实现Factorybean接口就是将 getObject()方法结果放入Spring的factoryBeanObjectCache这个容器里,我们根据类实际的beanName获得的bean其实是getObject()方法返回的bean,如果要获取类真正的实例Bean,需要在beanName前加个&符号,这种机制目前确实不知道有什么用。

总之,源码内容不要过分的深究,要一遍一遍的读,先从整体角度去考虑,然后针对细节做深化梳理,我总结的Spring相关内容也是基于以广度为先,按照流程的先后在细致的对每一个我想知道的知识点进行深度梳理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值