前言:继续前一章。
一、porfile 属性的使用
如果你使用过SpringBoot, 你一定会知道porfile配置所带来的方便, 通过配置开发环境还是生产环境, 我们可以十分方便的切换开发环境,部署环境,更换不同的数据库。 可能为了让Java开发者转向SpringBoot开发, Spring在5.x之后停止了对这个属性的支持。所以本文也就不再继续描述这一属性。
二、bean标签的解析及注册
Spring中的标签分为默认标签和自定义标签两种,而这两种标签的用法及解析方式存在着很大的不同,默认标签是在parseDefaultElement中进行的,函数中的功能一目了然,分别对4种标签(import, alias、bean、beans)做了不同的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
我们不得不承认,Spring5.x提倡我们更少的使用xml文件,而是更多的使用注解进行配置,而且如果你经常使用Springboot的话,那么你肯定知道习惯优于约定,并且springboot中只需要一个配置文件,虽然这有时根本无法满足需求,这里不做关于springboot的更多的说明。不过这并不影响Spring内部的实现,现在主要还是从xml文件分析一下bean标签的解析及注册。
在4中标签的解析中,对bean标签的解析最为复杂也最为重要, 所以我们从这个标签进行深入的分析。不过在这之前我还是要将之前怎么加载这个文件的部分进行一下回忆
还记得上一部分,有一个这样的方法:
/**
* This implementation parses bean definitions according to the "spring-beans" XSD
* (or DTD, historically).
* <p>Opens a DOM Document; then initializes the default settings
* specified at the {@code <beans/>} level; then parses the contained bean definitions.
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
如果确实对一步感兴趣可以追溯下去,这样就可以发现下面这段代码:
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这段代码可能有点难以理解,不过当知道了if(delegate.isDefaultNamespace(ele)) 这个方法的作用就知道了,这其实就是在对标签进行一次处理而已, 是默认标签的就交给默认的处理方式,是自定义标签的话就换另一种处理方式。这就是这个方法中做的事了。
public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}
这里的Node节点定义了所有的Spring提供的默认标签的解析结果。
那parseDefaultElement(ele, delegate)这个方法又在做些什么呢?其实不过是对根级节点的标签进行解析分类而已,现在我们先分析一下bean标签, 所以现在只看针对于标签做了些什么。
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
进入这个方法
/**
* 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 {
// 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));
}
}
这里使用Spring源码深入解析的一段:
三、解析BeanDefiniton
这个部分是Spring解析配置文件的最重要的部分, 根本就是解析标签并加载, 在使用Spring进行配置的时候不难发现, 我们有以下几个重要的根级标签,bean, imort, alias, nested-beans, 下面的内容就主要介绍一下bean标签的解析。上一个小结的结尾部分已经涉及了这个的处理,继续上面的内容, 我们会发现,实际上Spring首先是先通过“Bean定义解析委托”来获得了一个BeanDefinitionHolder, 在上面的分析中,我们似乎只注意了Element,而忘记了委托的是什么时候出现的,事实上这个委托是在DefaultBeanDefinitionDocumentReader在这个类中的时候就已经创建了这个委托, 并且一直通过参数的方式保存着这个委托, 知道们希望获得一个BeanDefinitionHolder的时候才真正的发挥作用,那么这个委托具体是什么呢?这个委托的作用是状态的保存, 早在DefaultBeanDefinitionDocumentReader 这个类中使用的时候就通过xml解析的上下文,保存了bean标签中的所有状态,这些状态包括,
....
等等等……
那么BeanDefinitionHolder的作用又是什么呢? 通过这个holder, 可是实现注册的功能这是一个十分重要的功能,后面会具体分析这个功能。现在首先要看的是怎么获得的这个holder呢:
/**
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
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");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, 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.isTraceEnabled()) {
logger.trace("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;
}
此处引用Spring源码解析中的一段内容:
以上便是对默认标签解析的全过程了。当然,对Spring的解析犹如洋葱剥皮一样,一层一层的进行,尽管现在只能看到对属性id以及name的解析,但是很庆幸,思路我们已经了解了。在开始对属性进行全面分析之前, Spring在最外层做了一个当前成的功能架构, 在当前成完成的主要工作包括以下的内容。
(1)提取元素中的id和name属性。
(2)进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
(3)如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
(4)将检测到的信息封装到BeanDefintionHolder的实例中。
继续跟进代码:
/**
* Parse the bean definition itself, without regard to name or aliases. May return
* {@code null} if problems occurred during the parsing of the bean definition.
*/
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
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;
}
通过对代码的跟踪,事实上,我们很容易发现,这里的className就是从上一个方法中的通过解析别名得到beanName,在这里通过beanName又从已存的元素中获取得到的。同样的这个标签的父级元素parent也是这样获取得到。而接下来的操作也就是对各种属性的具体的解析操作了,诸如:me他, lookup-method, replace-method, property, qualifier子元素等。
BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和<bean>中的属性是一一对应的。其中RootBeanDefinition是最常用的实现类,它对应一般性的<bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而没有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRestry就像是Spring配置信息的内存数据库,主要是以map的形式保存。后续操作直接从BeanDefinitionRegistry中读取配置信息。
BeanDefinition 及其实现类 |
由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className, parent)的作用就是实现此功能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。