Spring bean定义解析源码分析

在上一篇Spring IOC容器启动简介中在ClassPathXmlApplicationContext的基础粗略的分析了IOC容器的启动过程,对一些比较复杂的步骤没有详细的说明,从本篇开始对其中的一些比较复杂的步骤进行分析。本篇对基于ClassPathXmlApplicationContext的IOC容器的bean定义的解析与加载过程进行分析。bean定义解析加载的简单时序图如下:

《Spring bean定义解析源码分析》

bean定义的解析通过XmlBeanDefinitionReader来完成,在解析前先做一些准备工作:1、设置环境变量(Environment)用来匹配在bean配置文件可能出现的一些占位符;2、设置资源定位器用来定位bean的xml定义文件;3、设置xml实体处理器辅助xml的解析工作。

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));

XmlBeanDefinitionReader通过DocumentLoader把bean的定义文件解析成一颗DOM树,然后对DOM中的各个结点就行解析。

XmlBeanDefinitionReader的doLoadBeanDefinitions方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		int validationMode = getValidationModeForResource(resource);
		Document doc = this.documentLoader.loadDocument(
				inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
		return registerBeanDefinitions(doc, resource);
	}
	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);
	}
}

接下来是对DOM树根节点中的各个结点进行解析:

首先解析检查是否存在profile属性,如果定义了profile属性,判断该profile和设置到坏境中的profile是否匹配,如果不匹配说明这颗子树定义的bean不适用于当前的运行坏境,跳过当前整个颗子树的解析,见DefaultBeanDefinitionDocumentReader类的doRegisterBeanDefinitions方法中的代码片段

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;
	}
}

循环各个子树,检查节点是默认的名称空间还是扩展的名称空间,比如<bean>、<property>是默认的名称空间,而<aop:xxx>、<context:xxx>这种标签是扩展的标签需要额外的标签处理器,默认标签的一级标签是<beans>,先看一下默认名称空间。

默认名称空间有几个二级标签import、alias、bean、beans。

import标签,读取resource属性,如果未设置解析报错,判断resource对应的路径是绝对路径还是相对路径,如果是相对路径需要把路径装换成完整的路径,然后递归调用loadBeanDefinitions

beans标签,直接递归调用doRegisterBeanDefinitions

alias标签,读取name和alias属性,把该别名映射注册到容器中

代码在DefaultBeanDefinitionDocumentReader的processAliasRegistration方法

protected void processAliasRegistration(Element ele) {
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		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 {
				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));
		}
	}

bean标签,解析bean标签下的子树是整个默认名称空间标签解析最核心的动作,import和beans标签最后也会走到这一步。bean标签的解析逻辑被委托给BeanDefinitionParserDelegate:

首先读取bean的id属性和name属性,分别代码bean的id和别名,name如果以逗号或分号分隔,分裂成别名数组。如果id没有设置,取第一个别名当做id,并且把这个别名从别名数组中删除。

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");
		}
	}
	
    ...
	
}

如果bean不是内部bean,检查id和别名是否已经被其它的bean使用,如果已经被占用,解析报错。否则把id和别名都放到一个usedNames集合属性中,声明这些id和别名已经被占用。之所以内部bean不用检查时因为内部bean的id是容器生成的,容器会保证不会发生重复。parseBeanDefinitionElement等方法中有一个containingBean参数,这个参数用来区分当前解析的bean是否是内部bean,如果是非空值说明是内部bean,内部bean和非内部bean解析时只做了两件不同的事情:1、id生成策略;2、内部bean需要继承其外部bean的scope。

protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
	String foundName = null;

	if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
		foundName = beanName;
	}
	if (foundName == null) {
		foundName = (String) CollectionUtils.findFirstMatch(this.usedNames, aliases);
	}
	if (foundName != null) {
		error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
	}

	this.usedNames.add(beanName);
	this.usedNames.addAll(aliases);
}

如果既没有设置id也没有设置别名,那么容器会给bean自动生成一个id,生成规则如下:

1、在定义bean时是否指定了class,如果指定了取类名,如果未指定,判断是否指定了parent,如果指定了取parent名+$child,如果未指定判断是否指定了factory bean,如果指定了取factory bean名+$created,如果都未指定说明配置有错误
2、如果是内部bean,取步骤一生成的名称+#+随机数
3、如果不是内部bean,如果步骤一生成的名称在容器中不存在,取步骤一生成的名称,否则取步骤一生成的名称+#计数,如果计数已经被使用再次计数直到计数未被使用为止

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition 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;
			}
		}
		
		...
	}

	return null;
}

public static String generateBeanName(
		BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
		throws BeanDefinitionStoreException {

	String generatedBeanName = definition.getBeanClassName();
	if (generatedBeanName == null) {
		if (definition.getParentName() != null) {
			generatedBeanName = definition.getParentName() + "$child";
		}
		else if (definition.getFactoryBeanName() != null) {
			generatedBeanName = definition.getFactoryBeanName() + "$created";
		}
	}
	if (!StringUtils.hasText(generatedBeanName)) {
		throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
				"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
	}

	String id = generatedBeanName;
	if (isInnerBean) {
		// Inner bean: generate identity hashcode suffix.
		id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
	}
	else {
		// Top-level bean: use plain class name.
		// Increase counter until the id is unique.
		int counter = -1;
		while (counter == -1 || registry.containsBeanDefinition(id)) {
			counter++;
			id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
		}
	}
	return id;
}

接下来解析其它属性和子标签比如class、parent、scope、lazy-init、lookup-method等,生成一个GenericBeanDefinition对象,并且把这些属性封装到GenericBeanDefinition对象相应的属性中,代码在BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法:

public AbstractBeanDefinition parseBeanDefinitionElement(
		Element ele, String beanName, BeanDefinition containingBean) {

	this.parseState.push(new BeanEntry(beanName));

	String className = null;
	if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
		className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
	}

	try {
		String parent = null;
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);

		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

		parseMetaElements(ele, bd);
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

		parseConstructorArgElements(ele, bd);
		parsePropertyElements(ele, bd);
		parseQualifierElements(ele, bd);

		bd.setResource(this.readerContext.getResource());
		bd.setSource(extractSource(ele));

		return bd;
	}
	catch (ClassNotFoundException ex) {
		error("Bean class [" + className + "] not found", ele, ex);
	}
	catch (NoClassDefFoundError err) {
		error("Class that bean class [" + className + "] depends on not found", ele, err);
	}
	catch (Throwable ex) {
		error("Unexpected failure during bean definition parsing", ele, ex);
	}
	finally {
		this.parseState.pop();
	}

	return null;
}

最后如果定义了修饰器BeanDefinitionDecorator,对bean定义进行修改修饰,AOP标签定义了修饰器,这个在后面的文章介绍,然后把解析出来的bean定义和别名都注册到容器中。

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.
			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));
	}
}

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

	// Register bean definition under primary name.
	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 aliase : aliases) {
			registry.registerAlias(beanName, aliase);
		}
	}
}

以上是默认名称空间的标签解析,如果名称DOM子树根节点的名称空间是扩展的,那么进入BeanDefinitionParserDelegate的parseCustomElement方法,大致时序图如下:

《Spring bean定义解析源码分析》

首先需要查找一个名称空间处理器,代码如下:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

public DefaultNamespaceHandlerResolver() {
	this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
				    //handlerMappingsLocation=DEFAULT_HANDLER_MAPPINGS_LOCATION
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded NamespaceHandler mappings: " + mappings);
					}
					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
					this.handlerMappings = handlerMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.handlerMappings;
}

名称空间和名称空间处理器的映射关系定义在jar包的META-INF/spring.handlers文件中,比如spring-context-3.2.9.RELEASE.jar的spring.handlers的内容如下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

我们可以看到context的名称空间对应的处理是ContextNamespaceHandler,jee名称空间的处理器是JeeNamespaceHandler等。框架把所有Spring相关jar包中的spring.handlers文件加载出来并且存储名称空间和处理器的映射关系。找到处理器之后调用处理器的parse方法。

public BeanDefinition parse(Element element, ParserContext parserContext) {
	return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
	String localName = parserContext.getDelegate().getLocalName(element);
	BeanDefinitionParser parser = this.parsers.get(localName);
	if (parser == null) {
		parserContext.getReaderContext().fatal(
				"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
	}
	return parser;
}

从上面代码可以看出首先定位出待解析的标签对应的解析器,以ContextNamespaceHandler为例,从ContextNamespaceHandler的代码可以看出context名称空间下所有标签的解析器。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

找到解析器之后再调用解析器的parse方法解析具体的标签,比如annotation-config标签的解析器是AnnotationConfigBeanDefinitionParser,代码如下,在解析时遇到annotation-config标签时会注册ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor等BPP到容器中。

public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		Object source = parserContext.extractSource(element);

		// Obtain bean definitions for all relevant BeanPostProcessors.
		Set<BeanDefinitionHolder> processorDefinitions =
				AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

		// Register component for the surrounding <context:annotation-config> element.
		CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
		parserContext.pushContainingComponent(compDefinition);

		// Nest the concrete beans in the surrounding component.
		for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
			parserContext.registerComponent(new BeanComponentDefinition(processorDefinition));
		}

		// Finally register the composite component.
		parserContext.popAndRegisterContainingComponent();

		return null;
	}

}

关于扩展名称空间下的标签就不一个个分析,知道如何找相关的代码就好。

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/pentiumchen/article/details/43926975
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞