Spring源码深度解析系列——bean标签解析《二》

导语

前一篇文章中,已经把bean的属性解析完成了,也讲到了把xml文档转换为GenericBeanDefinition,这样在GenericDefinition中可以找到对应的配置。但是GenericDefinition也只是子实现类,大部分的属性还是保存在AbstractBeanDefinition中的,那么可以来先看下AbstractBeanDefinition中的基本属性来了解一下在xml中解析了那些对应的配置。

	/**
	 * bean的作用范围,对应bean属性scope
	 * 包含是否是单例、是否是原型
	 */
	private String scope = SCOPE_DEFAULT;

	// 是否抽象,对应bean属性abstract
	private boolean abstractFlag = false;

	// 是否延迟加载,对应bean属性lazy-init
	private boolean lazyInit = false;

	// 自动注入模式,对应bean属性autowire
	private int autowireMode = AUTOWIRE_NO;

	// 依赖检查,Spring3.0以后启用这个属性
	private int dependencyCheck = DEPENDENCY_CHECK_NONE;

	// 用来表示一个bean的实例化依靠另一个bean先实例化,对应bean属性depend-on
	private String[] dependsOn;

	/**
	 * autowire-candidate属性设置为false,这样容器在查找自动配置对象时,
	 * 将不考虑该bean,即它不会被考虑作为其他bean自动装配候选者,但是该bean本身还可以使用自动装配来注入其他bean的。
	 * 对应bean属性autowire-candidate
	 */
	private boolean autowireCandidate = true;

	// 自动装配时出现多个bean候选者时,将作为首选者,对应bean属性primary
	private boolean primary = false;

	// 用于记录Qualifier,对应子元素qualifier
	private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();


	private Supplier<?> instanceSupplier;

	// 允许访问非公开的构造器和方法,程序设置
	private boolean nonPublicAccessAllowed = true;

	/**
	 * 是否以一种宽松的模式解析构造函数,默认为true,如果为false,则如下情况:
	 * interface ITest{}
	 * class ITestImpl implement ITest {}
	 * class Main {
	 *     Main(ITest i){}
	 *     Main(ITestImpl i){}
	 * }
	 */
	private boolean lenientConstructorResolution = true;


	private String factoryBeanName;


	private String factoryMethodName;

	// 记录构造函数注入属性,对应bean属性constructor-arg
	private ConstructorArgumentValues constructorArgumentValues;

	// 普通属性集合
	private MutablePropertyValues propertyValues;

	// 方法重写的持有者,记录lookup-method、replaced-method
	private MethodOverrides methodOverrides;

	// 初始化方法,对应bean属性init-method
	private String initMethodName;

	// 销毁方法,对应bean属性destroy-method
	private String destroyMethodName;

	// 是否执行init-method,程序设置
	private boolean enforceInitMethod = true;

	// 是否执行destroy-method,程序设置
	private boolean enforceDestroyMethod = true;

	// 是否用于用户定义的而不是应用程序本身定义的,创建AOP的时候为true,程序设置
	private boolean synthetic = false;

	// 定义这个bean的应用,APPLICATION:用户,INFRASTRUCTURE:完全内部使用,与用户无关,SUPPORT:某些复杂配置的一部分,程序设置
	private int role = BeanDefinition.ROLE_APPLICATION;

	// bean的描述信息
	private String description;

	// 这个bean定义的资源
	private Resource resource;

一、解析默认标签中的自定义标签

了解完AbstractBeanDefinition的基本属性之后,那么同时也完成了对分析默认标签和提取的过程了,由于前面所涉及的代码很复杂,在这里先来回顾一下默认标签解析的方法:

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		// 进行元素解析
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		// 如果不为空,说明还有自定义的标签,然后对其进行解析
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				// 注册bdHolder
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			// 发出响应事件,通知相关的监听程序,这个Bean已经记载完成
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

同样,在前面的文章中,已经对parseBeanDefinitionElement(ele)方法的作用进行了详细的描述,这里就不再赘述了。本篇将从decorateBeanDefinitionIfRequired(ele, bdHolder)方法入手,从语义上看,这里的意思说如果需要的话,就对BeanDefinition进行装饰。对于这行代码的具体作用,先看下下面这段代码:

    <bean id="test" class="com.zfy.spring.test.bean.User">
        <mybean:user username="test"/>
    </bean>

在Spring中的bean使用的是默认标签配置,不过当其子元素却使用了自定义的配置后,这行代码便会执行了。但是很奇怪的是,为什么这行代码为什么会处于默认标签解析的逻辑之中?因为前面说过对于bean的解析有两种类型,分别是:默认类型解析和自定义类型。其实前面所讲的两种类型只是针对于bean而言,但是如果细看,会发现这里却并非如此,这里的自定义类型时就属性而言的。那我们继续看代码:

	public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
		return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
	}

可以看到这里回调的方法中的第三个参数,所传的值是为null的,那么这个参数的作用又是什么呢?什么样的情况下,这个参数才不会为null呢?其实这个参数是父类bean。在对某个嵌套配置进行分析时,这里就需要传入父类BeanDefinition。分析源码后,得知这里传递参数是为了使用父类的scope属性,当子类没有设置scope属性的时候,便会默认使用父类的scope了。但这里分析是是顶层的配置,这也就是这里为什么传null的原因了。继续进入这个方法:

	public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {

		BeanDefinitionHolder finalDefinition = definitionHolder;

		// Decorate based on custom attributes first.
		NamedNodeMap attributes = ele.getAttributes();
		// 遍历所有的属性,看是否有适用于修饰的属性
		for (int i = 0; i < attributes.getLength(); i++) {
			Node node = attributes.item(i);
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}

		// Decorate based on custom nested elements.
		// 遍历所有子节点,看是否有适用于子元素的
		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;
	}

上面这段代码主要就是对元素中所有的属性和子节点进行decorateIfRequired(node, finalDefinition, containingBd)方法的调用,那么继续这段代码的解析:

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

		// 获取自定义标签的命名空间
		String namespaceUri = getNamespaceURI(node);
		// 对于非默认标签进行修饰
		if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
			// 根据命名空间找到对应的处理器
			NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
			if (handler != null) {
				// 进行修饰
				BeanDefinitionHolder decorated =
						handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
				if (decorated != null) {
					return decorated;
				}
			}
			else if (namespaceUri.startsWith("http://www.springframework.org/")) {
				error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
			}
			else {
				// A custom namespace, not to be handled by Spring - maybe "xml:...".
				if (logger.isDebugEnabled()) {
					logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
				}
			}
		}
		return originalDef;
	}

这段代码已经很清楚的告诉我们了,首先获取属性或者元素的命名空间,以此来判断该元素或属性是否满足于自定义标签解析的条件,如果满足就找出自定义类型的NamespaceHandler来进一步进行解析。这里暂且略过,不作解析。

从上面代码中是可以看出,对于默认的标签处理是直接略过,因为前面默认标签已经处理完毕了。这里只是对自定义的标签或bean的自定义属性进行操作。

二、注册解析的BeanDefinition

到这里配置文件基本上该解析的都已经解析了,该装饰的也都装饰了。这里所得到的BeanDefinition也已经满足后续的基本需求了,现在所剩下的工作就是注册了。也就是processBeanDefinition方法中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())操作了,代码如下:

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

		// Register bean definition under primary name.
		// 使用beanName做唯一标识注册
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		// 注册所有的别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

从上面代码中,可以看出所有解析后的BeanDefinition都会被注册到BeanDefinitionRegistry中去,但对于beanDefinition的注册而言是分为两个部分的:一是通过beanName注册,二是通过别名注册。

1.通过beanName注册BeanDefinition

在我们惯性思维中,对于BeanDefinition的注册,认为只是把beanDefinition直接放入map中就好了,其实不仅如此,它还有其他的操作:

    // Implementation of BeanDefinitionRegistry interface
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				/**
				 * 注册前最后一次校验,这里的校验不同与之前的XML文件校验,
				 * 主要是对于AbstractBeanDefinition属性中的methodOverride校验,
				 * 校验methodOverride是否与工厂方法并存或者methodOverride对应的方法根本不存在
				 */
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + existingDefinition + "] bound.");
			}
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isWarnEnabled()) {
					logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(existingDefinition)) {
				if (logger.isInfoEnabled()) {
					logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				// 因为BeanDefinitionMap是全局变量,这里定会存在并发访问的情况
				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
				// 注册BeanDefinition
				this.beanDefinitionMap.put(beanName, beanDefinition);
				// 记录beanName
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (existingDefinition != null || containsSingleton(beanName)) {
			// 重置所有的beanName对应的缓存
			resetBeanDefinition(beanName);
		}
	}

上面代码的基本步骤如下:

  1. 对AbstractBeanDefinition的校验,在解析的时候也有校验,但这两个校验的性质并非医药,之前只是校验XML的格式,这里确实针对AbstractBeanDefinition的methodOverride的属性而言。
  2. 对一些已经处于注册状态的beanName进行处理,如果配置文件中设置了不允许覆盖,则抛出异常,反之直接覆盖。
  3. 加入map缓存。
  4. 清除解析之前六次啊的beanName缓存。

2.通过别名注册BeanDefinition

在理解前面的代码后,这里关于别名注册的代码就相对容易些了。

SimpleAliasRegistry.java:

	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			// 如果beanName与alias相同的话则不记录alias,并删除对应的alias
			if (alias.equals(name)) {
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}
			else {
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
					if (registeredName.equals(name)) {
						// An existing alias - no need to re-register
						return;
					}
					// 如果alias不允许被覆盖,则抛出异常
					if (!allowAliasOverriding()) {
						throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
					if (logger.isInfoEnabled()) {
						logger.info("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'");
					}
				}
				// 当A->B存在时,若在出现A->B->C 的时候则会抛出异常
				checkForAliasCircle(name, alias);
				this.aliasMap.put(alias, name);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}

上面代码的执行步骤如下:

  1. alias与beanName相同处理:如果alias与beanName并名称相同则不需要处理并删除原有的alias。
  2. alias覆盖:如果aliasName已经使用,且已经指向了另一个beanName,则需要用户是设置进行处理。
  3. alias循环检查:当A->B存在时,若在出现A->B->C 的时候则会抛出异常。
  4. 注册alias。

三、解析alias标签

经过前面的长途跋涉,终于完成了bean标签的处理,前面说过配置文件的解析是包括对import标签、alias标签、bean标签、beans标签。不过我们这最核心的bean标签的解析也已可以告一段落了,而其他的几个标签是围绕着bean标签的解析而进行的,所以当我们解析完bean标签后,再来看下alias标签的解析吧!

在平常的使用中我们都是通过id的属性来指定名称,但是在提供多个名称的时候,我们就可以使用alias了,具体使用如下:

    <bean id="test" class="com.test"/>
    <alias name="test" alias="test1,test2"/>

当然还有更复杂的情况,这里就不赘述了,还是先看下alias标签解析的代码吧!

DefaultBeanDefinitionDocumentReader:

	protected void processAliasRegistration(Element ele) {
		// 获取beanName
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		// 获取alias
		String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
		boolean valid = true;
		if (!StringUtils.hasText(name)) {
			getReaderContext().error("Name must not be empty", ele);
			valid = false;
		}
		if (!StringUtils.hasText(alias)) {
			getReaderContext().error("Alias must not be empty", ele);
			valid = false;
		}
		if (valid) {
			try {
				// 注册alias
				getReaderContext().getRegistry().registerAlias(name, alias);
			}
			catch (Exception ex) {
				getReaderContext().error("Failed to register alias '" + alias +
						"' for bean with name '" + name + "'", ele, ex);
			}
			// 别名注册后通知监听器做相应的处理
			getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
		}
	}

四、解析import标签

在大型项目中,我们会使用到分模块的,但是分模块的解决方法就有很多,不过import却是这众多方法中不错的一种方法。那我们来看下代码是如何实现的吧!

	protected void importBeanDefinitionResource(Element ele) {
		// 获取resource的属性
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		// 如果不存在resource属性则不做任何处理
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}

		// Resolve system properties: e.g. "${user.dir}"
		//解析系统属性,格式如:"${user.dir}"
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

		Set<Resource> actualResources = new LinkedHashSet<>(4);

		// Discover whether the location is an absolute or relative URI
		// 判定location是绝对的URI还是相对URI
		boolean absoluteLocation = false;
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {
			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
		}

		// Absolute or relative?
		// 如果是绝对URI则直接根据地址加载对应的配置文件
		if (absoluteLocation) {
			try {
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
				}
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
			}
		}
		else {
			// No URL -> considering resource location as relative to the current file.
			// 如果是相对地址,则根据相对根据相对地址计算出绝对地址
			try {
				int importCount;
				// Resource存在多个子实现类,如VfResource、FileSystemResource等,
				// 而每个resource的createRelative方式实现不一样,所以这里先使用子类的方法尝试解析
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				if (relativeResource.exists()) {
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
				else {
					// 如果解析不成功,则使用默认的解析器ResourcePatternResolver进行解析
					String baseLocation = getReaderContext().getResource().getURL().toString();
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
				}
			}
			catch (IOException ex) {
				getReaderContext().error("Failed to resolve current resource location", ele, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
						ele, ex);
			}
		}
		// 解析后进行监听器激活处理
		Resource[] actResArray = actualResources.toArray(new Resource[0]);
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

上面代码的基本步骤如下:

  1. 获取resource属性所表示的路径。
  2. 解析路径中的系统属性,格式如“${user.dir}”。
  3. 判定location是绝对路径还是相对路径。
  4. 如果是绝对路径则递归调用bean的解析过程,进行另一次解析。
  5. 如果是相对路径,则计算出绝对路径并解析。
  6. 通知监听后解析完成。

本文到这里也已经快结束了,会有人发现是不是还有一个关于beans标签的解析,其实对于beans标签而言,它是一种嵌入式的,和单独文件并没有太多的区别,从代码上来看,无非就是递归调用 doRegisterBeanDefinitions(ele) 方法。既然如此,那这里就不再赘述了。

参考:Spring源码深度解析(第二版)(郝佳)

发布了41 篇原创文章 · 获赞 8 · 访问量 4262

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/94558342