likes
comments
collection
share

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

作者站长头像
站长
· 阅读数 37

✨这里是第七人格的博客✨小七,欢迎您的到来~✨

🍅系列专栏:Spring源码解析🍅

✈️本篇内容: 源码导读-Spring容器初始化流程-XmlBeanFactory✈️

🍱 本篇收录完整代码地址:gitee.com/diqirenge/s… 🍱

楔子

小七早在2021年的时候,就想学习输出一些关于Spring源码的文章了,然后开篇了如何下载编译Spring源码以后,就太监掉了。不要问小七为什么?问就是懒 ̄□ ̄||但是这一次,小七也要当一次真男人!

Spring 简单使用示例

随意编写一个类

public class MyBean {
    private String name = "myName";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在resources下添加资源文件spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<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="myBean" class="com.run2code.MyBean"/>

</beans>

编写启动类

/**
 * Spring 简单使用示例
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2024/01/19
 */
public class BeanFactoryDemo {

    private static final String FILE_NAME = "spring.xml";

    public static void main(String[] args) {
        XmlBeanFactory demo = createXmlBeanFactory(FILE_NAME);
        MyBean myBean = (MyBean) demo.getBean("myBean");
        System.out.println(myBean.getName());
    }

    public static XmlBeanFactory createXmlBeanFactory(String fileName) throws BeansException {
        return new XmlBeanFactory(new ClassPathResource(fileName));
    }

}

启动成功后控制台会打印出myName

myName

完整demo地址: gitee.com/diqirenge/s…

源码分析

从使用例子中我们可以知道,我们是先构建了一个XmlBeanFactory的对象,然后调用它的getBean方法,来获取bean的。那我们又是怎么构建出XmlBeanFactory的呢?是通过调用构造方法new ClassPathResource(fileName),传入文件名来构建ClassPathResource的。

new XmlBeanFactory(new ClassPathResource(fileName));

ClassPathResource又是一个什么东西呢?

观察XmlBeanFactory的构造方法,我们知道ClassPathResource本质上是个Resource

public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
}

Resource是个接口,他继承于InputStreamSource接口

public interface Resource extends InputStreamSource

而InputStreamSource很简单,他里面只有一个getInputStream()方法,返回了一个InputStream对象

InputStream getInputStream() throws IOException;

所以ClassPathResource本质上是要获取一个InputStream输入流

那我们还有必要在仔细研究ClassPathResource的源码吗?没有必要,因为我们的目的是探寻Spring容器的初始化流程,而不是研究资源是怎么加载的,所以抓大放小,我们只要了解他是要获取InputStream输入流就行了,接着我们返回XmlBeanFactory的构造方法。

public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
}

进入他重载的构造方法

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    // 1、调用父类的构造方法做一些事情
    super(parentBeanFactory);
    // 2、根据方法名是要去载入bean定义-BeanDefinitions
    this.reader.loadBeanDefinitions(resource);
}

很明显,构造方法分为两部分:

①调用父类的构造方法做一些事情

②根据方法名是要去载入bean定义-BeanDefinitions

我们先从第一步看起,看看调用父类的构造方法到底搞了什么飞机。

XmlBeanFactory→→→→→DefaultListableBeanFactory

很可惜,XmlBeanFactory的爸爸DefaultListableBeanFactory,也是直接调的他自己的爸爸

public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
    super(parentBeanFactory);
}

DefaultListableBeanFactory→→→→→AbstractAutowireCapableBeanFactory

public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
    // 1、调用重载的构造方法
    this();
    // 2、从方法名推测是给父类的bean赋值
    setParentBeanFactory(parentBeanFactory);
}

AbstractAutowireCapableBeanFactory的构造方法也是做了2个事情:

①调用重载的构造方法

②从方法名推测是给父类的bean赋值

我们分别瞄一眼这2个方法

1、构造方法

public AbstractAutowireCapableBeanFactory() {
    // 1、调用父类构造方法
    super();
    // 2、调用ignoreDependencyInterface方法
    ignoreDependencyInterface(BeanNameAware.class);
    ignoreDependencyInterface(BeanFactoryAware.class);
    ignoreDependencyInterface(BeanClassLoaderAware.class);
}

好吧,又是两步走

①找爸爸

public AbstractBeanFactory() {
    // 空的构造方法什么也没干
}

还好这一次AbstractBeanFactory是一个空的构造方法什么也没干,可以不用再找爸爸了

②调用ignoreDependencyInterface方法

这个方法的实现很简单,其实就是将类放到了Set集合中

public void ignoreDependencyInterface(Class<?> ifc) {
    this.ignoredDependencyInterfaces.add(ifc);
}

结合前面的构造方法,我们可以知道这里将BeanNameAware、BeanFactoryAware、BeanClassLoaderAware都放到了这个Set中。那这个方法是干什么的呢?方法的注释已经告诉你了啊,小可爱~

忽略给定的依赖注入接口进行自动装配。 这将通常由应用程序上下文用于注册通过其他方式解析的依赖项,例如通过BeanFactoryAware的BeanFactory或通过ApplicationContextAware的ApplicationContext。 默认情况下,只忽略BeanFactoryAware接口。要忽略更多类型,请为每种类型调用此方法。

码蛋,每个中文都认识,怎么连起来就看不懂了呢o(╥﹏╥)o

这个时候我们换个思路,既然ignoredDependencyInterfaces是个Set,那么我们看看有什么地方用到了他。

protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
    return (AutowireUtils.isExcludedFromDependencyCheck(pd) ||
          this.ignoredDependencyTypes.contains(pd.getPropertyType()) ||
          AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces));
}

还好,我们只在isExcludedFromDependencyCheck看到用到了ignoredDependencyInterfaces,并且我们只需要判断AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces)为true的情况就可以了。以下是该方法的详细注释:

// 判断给定的属性描述符(PropertyDescriptor)对应的setter方法是否在指定的接口集合中定义
public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Class<?>> interfaces) {
    // 获取属性描述符对应的setter方法
    Method setter = pd.getWriteMethod();
    // 如果setter方法不为空
    if (setter != null) {
        // 获取setter方法所在的类
        Class<?> targetClass = setter.getDeclaringClass();
        // 遍历接口集合
        for (Class<?> ifc : interfaces) {
            // 如果接口是目标类的父接口,并且接口中包含setter方法
            if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter)) {
                // 返回true,表示setter方法在接口中定义
                return true;
            }
        }
    }
    // 如果没有找到匹配的接口,返回false
    return false;
}

根据以上代码我们可以知道,在Spring中有一些接口的属性,比如:BeanNameAware、BeanFactoryAware、BeanClassLoaderAware等,是不可以由外部随意注入的。因为他们姓赵,天也奈何不了他们。

以下是示例代码:

①新建IgnoredDependencyInterfacesTest类,实现BeanNameAware接口

// 当实现了BeanNameAware接口时,输出的结果为:ignoredDependencyInterfacesTest
// 否则,输出的结果为:myName
public class IgnoredDependencyInterfacesTest implements BeanNameAware {
//public class IgnoredDependencyInterfacesTest {
    private String beanName = "myName";

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String name) {
        this.beanName = name;
    }
}

②在Spring.xml添加以下定义

<bean id="ignoredDependencyInterfacesTest" class="com.run2code.IgnoredDependencyInterfacesTest">
    <property name="beanName" value="myName"/>
</bean>

③在BeanFactoryDemo中编写测试代码

XmlBeanFactory demo = createXmlBeanFactory(FILE_NAME);
IgnoredDependencyInterfacesTest myBean = (IgnoredDependencyInterfacesTest) demo.getBean("ignoredDependencyInterfacesTest");
System.out.println(myBean.getBeanName());

④测试结果

ignoredDependencyInterfacesTest

我们定义的beanName:myName没有被注入

2、setParentBeanFactory方法

@Override
public void setParentBeanFactory(@Nullable BeanFactory parentBeanFactory) {
    if (this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) {
       throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory);
    }
    if (this == parentBeanFactory) {
       throw new IllegalStateException("Cannot set parent bean factory to self");
    }
    this.parentBeanFactory = parentBeanFactory;
}

因为我们从前面传进来的parentBeanFactory是空的,所以这块代码其实就是这样的

this.parentBeanFactory = null;

再瞟一眼parentBeanFactory,他其实就是个BeanFactory,再看看注释:父级bean工厂,用于支持bean继承。

/** Parent bean factory, for bean inheritance support. */
@Nullable
private BeanFactory parentBeanFactory;

好了,目前parentBeanFactory这玩意没有用到,查个眼,可能后面会用到。

第一条线我们分析完了,小结一下,其实调用父类的构造方法,主要就做了2件事情:

①忽略给定的依赖注入接口进行自动装配

②给父类的bean赋值

我们画一个小结的流程图如下:

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

再回忆一下这个构造方法

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    // 1、调用父类的构造方法做一些事情
    super(parentBeanFactory);
    // 2、根据方法名是要去载入bean定义-BeanDefinitions
    this.reader.loadBeanDefinitions(resource);
}

接下来我们分析第二条线

this.reader.loadBeanDefinitions(resource);

首先我们可以知道reader,是一个XmlBeanDefinitionReader的对象

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

并且这个this,指代的就是XmlBeanFactory自己,因为XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory实现了BeanDefinitionRegistry接口。

我们看一下这个构造方法,他其实是直接调用的父类的构造方法

public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    super(registry);
}

再观察一下AbstractBeanDefinitionReader的构造方法,其实就是初始化了一些属性,比如:registry、resourceLoader还有environment,但是我们现在还不知道这些属性有什么作用,所以先插个眼,混个眼熟。

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;

    // Determine ResourceLoader to use.
    if (this.registry instanceof ResourceLoader) {
       this.resourceLoader = (ResourceLoader) this.registry;
    }
    else {
       this.resourceLoader = new PathMatchingResourcePatternResolver();
    }

    // Inherit Environment if possible
    if (this.registry instanceof EnvironmentCapable) {
       this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
    }
    else {
       this.environment = new StandardEnvironment();
    }
}

然后我们继续分析调用的loadBeanDefinitions方法,可以看到loadBeanDefinitions中调用了另一个重载的方法loadBeanDefinitions,这个方法只是把入参从Resource包装成了EncodedResource

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

继续跟进代码,其实encoding、charset都是空的,也就是说默认的,我们没有为这个resource指定字符集和编码

private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
    super();
    Assert.notNull(resource, "Resource must not be null");
    this.resource = resource;
    this.encoding = encoding;
    this.charset = charset;
}

继续分析loadBeanDefinitions方法,重点关注doLoadBeanDefinitions

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

观察doLoadBeanDefinitions后,我们可以发现,其实有2个关键点:

①doLoadDocument方法

根据 inputSource和resource创建Document

②registerBeanDefinitions方法

注册bean定义

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
       throws BeanDefinitionStoreException {

    try {
       // 根据 inputSource和resource创建Document
       Document doc = doLoadDocument(inputSource, resource);
       // 注册bean定义
       int count = registerBeanDefinitions(doc, resource);
       if (logger.isDebugEnabled()) {
          logger.debug("Loaded " + count + " bean definitions from " + resource);
       }
       return count;
    }
    // 此处省略非重点代码
	...
}

首先我们来分析一下doLoadDocument方法,他最终是创建了一个Document对象,如果你有做过xml文件的解析,你一定对这个对象不陌生,你可以把它理解为一个包含了xml所有元素、属性和文本内容的接口,从概念上讲,它是文档树的根,并提供对文档数据的主要访问。说人话就是,你可以用它将student.xml和Student类相互转换。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
          getValidationModeForResource(resource), isNamespaceAware());
}

初略看一下DefaultDocumentLoader#loadDocument

this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
          getValidationModeForResource(resource), isNamespaceAware())

具体实现在DefaultDocumentLoader中,其实就是一些Document对象的组装,所以我们把重点转到参数的获取上,看这些参数都是怎么获取的,并且有什么作用。

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

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
       logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

接下来,我们来看看这些参数:

①inputSource是我们传进来的输入流,没什么好说的了。

②getEntityResolver(),从名字看,是去拿了解析器,并且点进去后,一眼看不出来,他是怎么获取解析器的,所以我们先插个眼,等一下应该是要重点关注的对象。

③this.errorHandler,点进去是一个日志处理器,不是很重要,先忽略。

private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);

④getValidationModeForResource(resource),获取资源验证模式,我们稍微看一下这个方法。

protected int getValidationModeForResource(Resource resource) {
    // 获取检验模式,默认就是VALIDATION_AUTO
    int validationModeToUse = getValidationMode();
    // 默认肯定相等,所以我们不会走这个分支
    if (validationModeToUse != VALIDATION_AUTO) {
       return validationModeToUse;
    }
    // 接下来需要看看这个方法是怎么处理的
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
       return detectedMode;
    }
    // Hmm, we didn't get a clear indication... Let's assume XSD,
    // since apparently no DTD declaration has been found up until
    // detection stopped (before finding the document's root tag).
    return VALIDATION_XSD;
}

很明显detectValidationMode(resource)是我们下一个需要关注的方法

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

点进方法后,我们简单分析一下,返回结果是VALIDATION_DTD或者VALIDATION_XSD,且他们受hasDoctype方法影响

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

hasDoctype方法比较简单,就是判断了是否包含了DOCTYPE这个常量

private boolean hasDoctype(String content) {
    return content.contains(DOCTYPE);
}

那么DOCTYPE是什么呢?就是一个DOCTYPE的字符

private static final String DOCTYPE = "DOCTYPE";

所以Spring是怎么验证资源模式的呢?就是通过xml文件中是否包含DOCTYPE字符串来判断的,如果包含,那么就是DTD,否则就是XSD。

⑤isNamespaceAware(),这个方法我们点进去,瞄一眼,其实很简单,默认情况下,就是fales。

大致分析了一遍后,返回我们刚刚插眼的地方,看看getEntityResolver()到底是个什么东西。

protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
       // Determine default EntityResolver to use.
       ResourceLoader resourceLoader = getResourceLoader();
       if (resourceLoader != null) {
          this.entityResolver = new ResourceEntityResolver(resourceLoader);
       }
       else {
          this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
       }
    }
    return this.entityResolver;
}

entityResolver是在这里赋值的,那他是ResourceEntityResolver还是DelegatingEntityResolver呢?这个取决于getResourceLoader的返回值。

public ResourceLoader getResourceLoader() {
    return this.resourceLoader;
}

看一看getResourceLoader,代码很简单,就是直接取的AbstractBeanDefinitionReader的resourceLoader属性

private ResourceLoader resourceLoader;

至于resourceLoader,这个玩意好像在我们前面分析reader的时候,看到过耶

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

我们去到AbstractBeanDefinitionReader的构造方法,果然是我们想要的东西

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

所以 getResourceLoader()得到的resourceLoader就是PathMatchingResourcePatternResolver,那么entityResolver就是ResourceEntityResolver了。

然后我们看看new ResourceEntityResolver(resourceLoader)做了些什么

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

public ResourceEntityResolver(ResourceLoader resourceLoader) {
    super(resourceLoader.getClassLoader());
    // 这里就是传递了一个对象,所以我们应该看看上面的方法,他的父类做了什么
    this.resourceLoader = resourceLoader;
}

他们的父类DelegatingEntityResolver,就初始化了2个解析器对象,结合我们前面分析过的Spring是如何获取资源验证模式的,我们这里可以大胆猜测,应该是初始化的DTD和XSD的解析器。

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
    this.dtdResolver = new BeansDtdResolver();
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

点进对象验证一下

①BeansDtdResolver是DTD的解析器

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

②PluggableSchemaResolver是XSD的解析器

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

至此,Document对象是怎么生成的,我们已经梳理清楚了,结下来我们来看看registerBeanDefinitions这个方法

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // ①创建一个BeanDefinitionDocumentReader对象
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // ②获取当前注册表中的Bean定义数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        // ③使用documentReader注册Bean定义,传入doc和创建的ReaderContext对象(基于resource)
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // ④返回注册后的Bean定义数量与之前的数量之差
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

通过方法名我们大概能够知道这个方法做了些什么事

①创建一个BeanDefinitionDocumentReader对象 ②获取当前注册表中的Bean定义数量 ③使用documentReader注册Bean定义,传入doc和创建的ReaderContext对象(基于resource) ④返回注册后的Bean定义数量与之前的数量之差

先看第一点,传入documentReaderClass,返回BeanDefinitionDocumentReader

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}

看看属性,可以确认传入的对象是DefaultBeanDefinitionDocumentReader

private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
       DefaultBeanDefinitionDocumentReader.class;

然后大致浏览一下BeanUtils.instantiateClass方法,我们可以知道,其实他的作用就是把传入的class,通过反射给生成对象来,所以我们这里返回的BeanDefinitionDocumentReader就是DefaultBeanDefinitionDocumentReader

接着再看第二点,获取当前注册表中的Bean定义数量,这一点已经很明确了,简单点进去看看就好。

然后在看第三点registerBeanDefinitions方法之前,我们先看看createReaderContext做了什么

public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
          this.sourceExtractor, this, getNamespaceHandlerResolver());
}

(一)源码导读-Spring容器初始化流程-XmlBeanFactory 也很简单,new了一个XmlReaderContext对象,初始化了一些我们从来没见过的参数,也不知道有啥用,所以老规矩,插个眼,混个眼熟。

然后看第三点registerBeanDefinitions,往下找我们能在DefaultBeanDefinitionDocumentReader看到registerBeanDefinitions方法

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

又是一个do开头的方法,经典名言:Spring中do开头的方法一般就是这个方法真正的实现

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

下面这3行代码异常瞩目

preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

其中preProcessXml和postProcessXml,都是protected级别的空方法,也就是说都是Spring留给我们的拓展点。

那我们其实要分析的方法就是parseBeanDefinitions方法了

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

首先我们来看一下,Spring是如何来判断是否是默认命名空间的,跟进delegate.isDefaultNamespace方法

public boolean isDefaultNamespace(Node node) {
    return isDefaultNamespace(getNamespaceURI(node));
}
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
    return !StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}

最后我们知道namespaceUri为空或者和BEANS_NAMESPACE_URI相等,那么就是默认的命名空间。而BEANS_NAMESPACE_URI是个常量,值为:www.springframework.org/schema/bean…所以namespaceUri为空或者等于http://www.springframework.org/schema/beans ,那么就是默认的命名空间。那么这个namespaceUri在哪里呢?这里贴出我们例子中的xml配置,读者可以自己思考一下(ps:xml的链接在idea下是可以点的哟)

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

默认的命名空间搞清楚了,接下来,我们继续看看他是怎么解析标签的。

①解析默认标签

parseDefaultElement(ele, delegate);

具体方法逻辑

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 解析 import 标签
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
       importBeanDefinitionResource(ele);
    }
    // 解析 alias 标签
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
       processAliasRegistration(ele);
    }
    // 解析 bean 标签
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
       processBeanDefinition(ele, delegate);
    }
    // 解析 beans 标签
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
       // recurse
       doRegisterBeanDefinitions(ele);
    }
}

我们这里选择bean这个分支来看看,Spring是如何解析的

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 解析bean标签,并封装到BeanDefinitionHolder中
    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));
    }
}

首先看看delegate.parseBeanDefinitionElement(ele)这个方法,他很简单,直接调用了parseBeanDefinitionElement(ele, null)这个方法

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

但继续跟进parseBeanDefinitionElement(ele, null),这个方法就显得稍稍有点复杂了,我为这块代码加了一些简单的注释,大家可以先看看

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 获取元素的id属性值
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 获取元素的name属性值
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // 创建一个别名列表
    List<String> aliases = new ArrayList<>();
    // 如果name属性有值,将其分割成数组并添加到别名列表中
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }

    // 将id作为beanName,如果没有指定id且别名列表不为空,则使用别名列表中的第一个作为beanName
    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");
        }
    }

    // 如果没有指定containingBean,检查beanName的唯一性
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    // 解析Bean定义元素,返回一个AbstractBeanDefinition对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    // 如果beanDefinition不为空
    if (beanDefinition != null) {
        // 如果没有指定beanName,尝试生成一个新的beanName
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // 注册一个别名,如果可能的话,为普通的bean类名,如果生成器返回了类名加上后缀。
                    // 这是为了兼容Spring 1.2/2.0的向后兼容性。
                    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);
        // 返回一个包含beanDefinition、beanName和别名数组的BeanDefinitionHolder对象
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    // 如果beanDefinition为空,返回null
    return null;
}

分析下来,我们可以猜测解析bean标签的关键方法应该是这个

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

继续跟进parseBeanDefinitionElement方法,代码行数也不少,我们先通览一遍,写上注释

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

    // 将beanName压入解析状态栈中
    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    // 如果元素有CLASS_ATTRIBUTE属性,则获取该属性值并去除空格
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    String parent = null;
    // 如果元素有PARENT_ATTRIBUTE属性,则获取该属性值
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {
        // 根据className和parent创建Bean定义对象
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 解析Bean定义的属性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        // 设置Bean定义的描述信息
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        // 解析meta元素
        parseMetaElements(ele, bd);
        // 解析lookup-method子元素
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 解析replaced-method子元素
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        // 解析constructor-args元素
        parseConstructorArgElements(ele, bd);
        // 解析property元素
        parsePropertyElements(ele, bd);
        // 解析qualifier元素
        parseQualifierElements(ele, bd);

        // 设置Bean定义的资源和源信息
        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        // 返回Bean定义对象
        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();
    }

    // 返回null表示解析失败
    return null;
}

parseState状态,目前我们没有用到,暂时可以不管。

继续往下面看,我们发现首先要获取一个bd对象,这里是返回的是一个抽象的AbstractBeanDefinition

AbstractBeanDefinition bd = createBeanDefinition(className, parent);

点进createBeanDefinition方法

protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
       throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(
          parentName, className, this.readerContext.getBeanClassLoader());
}

继续点进BeanDefinitionReaderUtils.createBeanDefinition方法

public static AbstractBeanDefinition createBeanDefinition(
       @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setParentName(parentName);
    if (className != null) {
       if (classLoader != null) {
          bd.setBeanClass(ClassUtils.forName(className, classLoader));
       }
       else {
          bd.setBeanClassName(className);
       }
    }
    return bd;
}

我们可以看到最后返回的AbstractBeanDefinition,其实真正的类型是GenericBeanDefinition

我们在idea中打开GenericBeanDefinition的类图,再加深一把对他的类继承关系的印象

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

然后返回上层,继续分析那个有点长的parseBeanDefinitionElement方法

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

再继续返回上层,简单看看decorateBeanDefinitionIfRequired。我们可以知道他其实是对标签进一步的解析和校验,如果发现bean标签中还存在自定义标签,就对自定义标签进行解析。

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

接下来再次返回上层,看看我们的bean定义是怎么用起来的

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

跟进BeanDefinitionReaderUtils.registerBeanDefinition方法

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 alias : aliases) {
          registry.registerAlias(beanName, alias);
       }
    }
}

我们发现,代码首先从BeanDefinitionHolder中拿到beanName,然后调用 registry.registerBeanDefinition,最后在对从BeanDefinitionHolder中拿到的别名列表进行处理(调用registry.registerAlias方法)。

所以我们先看看 registry.registerBeanDefinition方法是怎么实现的

// 注册Bean定义的方法
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    // 检查beanName是否为空,如果为空则抛出异常
    Assert.hasText(beanName, "Bean name must not be empty");
    // 检查beanDefinition是否为null,如果为null则抛出异常
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    // 如果beanDefinition是AbstractBeanDefinition的实例,则进行验证
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    // 获取已存在的Bean定义
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    // 如果已存在Bean定义
    if (existingDefinition != null) {
        // 如果不允许覆盖Bean定义
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            // 如果现有Bean定义的角色比新定义的角色低(例如,从ROLE_APPLICATION变为ROLE_SUPPORT或ROLE_INFRASTRUCTURE)
            if (logger.isInfoEnabled()) {
                logger.info("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (!beanDefinition.equals(existingDefinition)) {
            // 如果现有Bean定义与新定义不同
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        } else {
            // 如果现有Bean定义与新定义相同
            if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        // 将新的Bean定义添加到映射中
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        // 如果尚未开始创建Bean定义
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            // 无法再修改启动时集合元素(以获得稳定迭代)
            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;
                removeManualSingletonName(beanName);
            }
        } else {
            // 仍在启动注册阶段
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    // 如果已存在Bean定义或包含单例Bean,则重置Bean定义
    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    } else if (isConfigurationFrozen()) {
        // 清除类型缓存
        clearByTypeCache();
    }
}

根据以上方法我们可以知道,注册BeanDefinition,实际上就是在为一系列缓存属性赋值,而其中最值得我们关注的就是beanDefinitionMap,也就是我们常说的Spring容器,也叫Spring IOC。

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

他本质上是个map,为了避免线程安全问题,Spring使用的是ConcurrentHashMap。

到这里注册BeanDefinition这条路就走完了。

至于别名的注册,因为篇幅的问题,小七这里就不带着读者去看了,如果读者感兴趣可以自己去看看。

然后看看我们刚刚分析的方法

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

他最后一步就是发布一个BeanDefinition已经被注册的事件通知

getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

跟进fireComponentRegistered这个方法,我们其实可以发现这是一个空的实现。

最后我们完善一下我们的流程图,方便理解记忆

(一)源码导读-Spring容器初始化流程-XmlBeanFactory

转载自:https://juejin.cn/post/7328012551756283915
评论
请登录