前言:继续前一章。
一、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) 的作用就是实现此功能。