• 0

  • 199

Spring Bean 加载流程分析(通过 XML 方式加载)

智能的司机

我是老司机

2天前

Spring Bean 装配过程是一个老生常谈的话题,从最开始的通过 xml 形式装配 bean,到后来使用注解来装配 bean 以及使用 SpringBoot 和 SpringCloud 来进行开发,Spring 在整个过程中也进行了不断的演化和进步。不管是最初的 Spring 还是基于 Spring 开源的 SpringBoot、亦或是 SpringCloud,它们都是基于 Spring 的转变过来的,可能在 Spring 的基础上做了一些封装,但是本质上还是 Spring。

原本就没有那么多自动化的事情,只是 Spring 将实现的细节全部都隐藏在框架内部了。只有真正理解了 Spring,那么其实理解 SpringBoot 或者是 SpringCloud 只是一个水到渠成的事情。

起步

基于 Spring 版本 5.2.5

新建一个 maven 工程,导入如下几个 jar 包

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-beans</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
复制代码

在 classpath 路径下新建 applicationContext.xml 文件,配置一个新的 bean

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd">
    
    <bean id="user" class="com.liqiwen.spring.bean.User">
        <property name="id" value="23" />
        <property name="name" value="zhangsan" />
    </bean>
</beans>
复制代码

添加完毕后,工程示意图如下

通过 XML 形式装载 Bean

一个简单且基础获取 Bean 对象的代码示例如下:

public static void main(String[] args) {

    // Spring Bean 加载流程
    Resource resource = new ClassPathResource("applicationContext.xml");
    // 获取一个 BeanFactory
    DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
    // 定义 Bean 定义读取器
    BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
    // 从资源文件中读取 bean
    beanDefinitionReader.loadBeanDefinitions(resource);
    // 从工厂中获取 bean
    User user = (User) defaultListableBeanFactory.getBean("user");

    System.out.println(user.getId() + " =" + user.getName());
}
复制代码

接下来我们逐行来分析 Bean 是如何被 Spring 容器所装载并缓存的。

1. 定义资源对象

Resource resource = new ClassPathResource("applicationContext.xml");
复制代码

将 classpath 下的 applicationContext.xml 文件转换成 Resource 文件。Resource 也是 Spring 提供的一种资源接口,除了我们示例中使用的 ClassPathResource 外,Spring 也提供了其他形式的 Resource 实现类。

进入 new ClassPathResource("applicationContext.xml") 构造方法,看看构造方法做了什么处理?

public ClassPathResource(String path) {
    this(path, (ClassLoader) null);
}
复制代码

发现该构造方法调用了自身的另一个构造方法

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
    Assert.notNull(path, "Path must not be null");
    String pathToUse = StringUtils.cleanPath(path);
    if (pathToUse.startsWith("/")) {
        pathToUse = pathToUse.substring(1);
    }
    this.path = pathToUse;
    // 获取了一个类加载器
    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
复制代码

通过该构造方法,我们可以知道该构造方法初始了一个类加载器。如果类加载器不为空,则赋值成默认的类加载器。如果类加载器为空,则通过 ClassUtils.getDefaultClassLoader() 方法获取一个默认的类加载器。而我们传入的类加载器显然为 null,则 Spring 会去自动获取默认的类加载器。

public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        //  1.获取当前线程的类加载器
        cl = Thread.currentThread().getContextClassLoader();
    }
    catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        // 2. 获取当前类的类加载器
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoader
            try {
                //获取系统级的类加载器/应用类加载器 AppClassLoader
                cl = ClassLoader.getSystemClassLoader();
            }
            catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
            }
        }
    }
    return cl;
}
复制代码

通过 getDefaultClassLoader 方法我们可以知道,Spring 在获取类加载器做了如下三件事:

  • 获取当前线程的类加载器,如果存在,则返回。不存在则往下执行
  • 获取加载当前类的类加载器,如果存在,则返回。不存在则往下执行
  • 如果以上两步均没有获取到类加载器,则获取系统级类加载器/应用类加载器。

这里很好的利用了一个回退机制,用一个通俗的话语来解释回退机制就是退而求其次。先获取最合适的 xxx。如果获取不到,再获取其次合适的 xx。如果还是获取不到,就再退一步获取 x。

2. 初始化 BeanFactory

接下来我们看示例代码中的第二行代码的实现

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
复制代码

看看 new DefaultListableBeanFactory() 方法做了什么?

/**
 * 创建一个默认的 BeanFactory
 * Create a new DefaultListableBeanFactory.
 */
public DefaultListableBeanFactory() {
    super();
}
复制代码

一个空的实现,但是调用了父类的构造方法,跟进一步,看看父类的构造方法做了什么事情。

public AbstractAutowireCapableBeanFactory() {
    super();
    ignoreDependencyInterface(BeanNameAware.class);
    ignoreDependencyInterface(BeanFactoryAware.class);
    ignoreDependencyInterface(BeanClassLoaderAware.class);
}
复制代码

同样,该构造方法也调用了父类的构造方法,跟进父类的构造方法一探究竟。

public AbstractBeanFactory() {
}
复制代码

空实现,没啥好看的。看看 AbstractAutowireCapableBeanFactory 里面的另外三个方法。通过方法的名称我们可以大致猜出,这是为了忽略某些特定的依赖接口。

private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>();
public void ignoreDependencyInterface(Class<?> ifc) {
    this.ignoredDependencyInterfaces.add(ifc);
}
复制代码

没有太多的复杂逻辑,只是将某些特定的 class 对象放进了一个 set 集合中,标记这些接口应该被忽略,或许这个 set 集合会在后面的某一处使用到。但是注意,只有 BeanFactory 的接口应该被忽略。

3. 定义 BeanDefinitionReader 对象

接下来我们看第三行代码的实现

BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
复制代码

通过该行代码,我们定义了一个 Bean 定义的读取器,将第二步生成的 defaultListableBeanFactory 对象传入我们定义的读取器。

/**
 * Create new XmlBeanDefinitionReader for the given bean factory.
 * @param registry the BeanFactory to load bean definitions into,
 * in the form of a BeanDefinitionRegistry
 */
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    super(registry);
}
复制代码

该方法是为给定的 BeanFactory 创建一个 BeanDefinitionReader。这里我们可以看到构造方法的入参类型是 BeanDefinitionRegistry 类型,为什么我们定义的 DefaultListableBeanFactory 也能传入进去?很显然我们的 DefaultListableBeanFactory 实现了该接口。我们看看 DefaultListBeanFactory 的继承图。

XMLBeanDefinitionReader 的构造方法调用了父类的构造方法,跟进去父类的构造方法看看。

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

    // Determine ResourceLoader to use. 决定要使用的 ResourceLoader
    // 1. 根据传入进来的 BeanDefinitionRegistry 来获取 ResourceLoader
    if (this.registry instanceof ResourceLoader) {
        this.resourceLoader = (ResourceLoader) this.registry;
    } else {
        this.resourceLoader = new PathMatchingResourcePatternResolver();
    }

    // Inherit Environment if possible 继承环境如果存在的话
    // 2. 根据传入进来的 BeanDefinitionRegistry 来获取当前的环境
    if (this.registry instanceof EnvironmentCapable) {
        this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
    } else {
        this.environment = new StandardEnvironment();
    }
}
复制代码

该构造方法做了如下的几件事情

  • 给当前类的 registry 类型赋值
  • 根据传入进来的参数来获取对象的 ResourceLoader
  • 根据传入进来的参数来获取当前的环境
public PathMatchingResourcePatternResolver() {
    this.resourceLoader = new DefaultResourceLoader();
}
复制代码

显然传入进来的 BeanDefinitionRegistry 不是 ResourceLoader 的实现类,这个我们从类的继承图中可以看出来。因此当前类的 ResourceLoader 为 new PathMatchingResourcePatternResolver();,该方法获取了默认的 ResourceLoader。

public PathMatchingResourcePatternResolver() {
    this.resourceLoader = new DefaultResourceLoader();
}
public DefaultResourceLoader() {
    // 返回了一个默认的 ResourceLoader,并且赋值当前类的 classLoader
    this.classLoader = ClassUtils.getDefaultClassLoader();
}
复制代码

前面做了这么多的准备工作,接下来开始真正从我们定义好的 applicationContext.xml 来加载 Bean 的定义。该功能通过 beanDefinitionReader.loadBeanDefinitions(resource); 来实现。

4. 从给定的资源文件中加载 BeanDefinition

看看 beanDefinitionReader.loadBeanDefinitions(resource); 是如何加载 Bean 的定义的。

Spring 对方法名称的命名比较有讲究,基本上可以做到见名之意。通过方法名我们就可以知道 loadBeanDefinitions 是加载 BeanDefinition,但是注意:这里使用了复数,说明这里加载的 BeanDefinition 可能会存在多个。

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}
复制代码

该方法的主要功能是为了从指定的 Resource 文件中加载 BeanDefinition,该方法的返回值是返回 BeanDefinition 的数量。此处 Spring 将传入的 Resource 对象封装成了一个 EncodedResource 对象。顾名思义我们知道该对象只是对 Resource 进行了封装,其中除了包含指定的 Resource 资源外,还包含了编码信息,进入 EncodedResource 源码看看。

public EncodedResource(Resource resource) {
    this(resource, null, null);
}
public EncodedResource(Resource resource, @Nullable String encoding) {
    this(resource, encoding, null);
}
public EncodedResource(Resource resource, @Nullable Charset charset) {
    this(resource, null, charset);
}
/**
 * 私有构造方法
 **/
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;
}
复制代码

可以看到,Charset 和 encoding 是互斥的属性。显然我们这里仅仅只传入了 Resource 对象,那么默认的 encoding 了 charset 均为空。

接下来看看 loadBeanDefinitions(EncodedResource e) 的具体实现。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    // .... 省略无效代码
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
  }
复制代码

resourcesCurrentlyBeingLoaded 是一个 ThreadLocal 对象,首先先从 resourcesCurrentlyBeingLoaded 中获取当前的 encodedResource,如果获取出来的为空,则初始化一个 new HashSet<EncodedResource> 对象,将其放置到 resourcesCurrentlyBeingLoaded 对象中。接下来判断该对象是否在 resourcesCurrentlyBeingLoaded 中的 set 集合中已经存在,如果存在,则抛出 BeanDefinitionStoreException 异常,那么这个异常会在何时出现呢?我们可以尝试将 applicationContext.xml 进行改造一下。

<!-- 通过 import 组件导入自身的配置文件 -->
<import resource="applicationContext.xml" />

<bean id="user" class="com.liqiwen.spring.bean.User">
    <property name="id" value="23" />
    <property name="name" value="zhangsan" />
</bean>
复制代码

很显然这里面造成了一个循环引用,执行至此必然会抛出异常。

这里使用了一种巧妙的方式,通过 Set 集合不能有重复数据的特性来判断 applicationContext.xml 文件中的定义是否出现了循环导入。

接下来看看 doLoadBeanDefinitions 的具体实现

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        return count;
    }
   // ... 省略部分代码
}
复制代码

显然 doLoadBeanDefinitions 做了两件事情

  • 从给定的 Resource 资源中读取 XML 文件中的内容,该方法返回了一个 Document 对象
  • 通过 Document 对象和给定的 Resource 资源中注册 bean 的定义

对于 doLoadDocument 方法的读取,实际上就是读取 XML 里面的内容,并返回一个 Document 对象。这部分就不跟源码进去看了,有兴趣可以自己搜索一下 XML 解析相关的内容。

下面看看 registerBeanDefinitions 相关的源码,看看是如何从 document 中获取到注册到 bean 的定义的。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 创建了一个 Bean 定义文档读取器
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 获取到工厂中已经获取到 Bean 定义的数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 注册 Bean 定义
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 返回本次要注册 bean 定义的数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
复制代码

看看如何获取一个 Bean 定义文档读取器(BeanDefinitionDocumentReader)

private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
            DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}
复制代码

这里看到了调用了 Spring 自身提供的一个 BeanUtils.instantiateClass 方法。传入了 DefaultBeanDefinitionDocumentReader 的 class 文件,稍加思考我们便可以知道该方法是通过反射的方式生成了 BeanDefinitionDocumentReader 这个对象的实例。下面去 BeanUtils.instantiateClass 源码验证一下。

public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
  Assert.notNull(clazz, "Class must not be null");
  // 如果传入的是一个接口,则抛异常
  if (clazz.isInterface()) {
      throw new BeanInstantiationException(clazz, "Specified class is an interface");
  }
  try {
      // 实例化类,获取构造器
      return instantiateClass(clazz.getDeclaredConstructor());
  }
  catch (NoSuchMethodException ex) {
      // 对 Kotlin 的支持
      Constructor<T> ctor = findPrimaryConstructor(clazz);
      if (ctor != null) {
          return instantiateClass(ctor);
      }
      throw new BeanInstantiationException(clazz, "No default constructor found", ex);
  }
  catch (LinkageError err) {
      throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
  }
}
复制代码

看看重载的 instantiateClass 方法,一目了然,全部都是反射相关的内容。

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    Assert.notNull(ctor, "Constructor must not be null");
    try {
        // 设置 makeAccessible 属性为 true,
        ReflectionUtils.makeAccessible(ctor);
        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
            return KotlinDelegate.instantiateClass(ctor, args);
        }
        else {
            Class<?>[] parameterTypes = ctor.getParameterTypes();
            Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
            Object[] argsWithDefaultValues = new Object[args.length];
            for (int i = 0 ; i < args.length; i++) {
                if (args[i] == null) {
                    Class<?> parameterType = parameterTypes[i];
                    argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
                }
                else {
                    argsWithDefaultValues[i] = args[i];
                }
            }
            return ctor.newInstance(argsWithDefaultValues);
        }
    }
    // ... 省略部分代码
}
复制代码

注意在该方法的头部调用了 ReflectionUtils.makeAccessible(ctor); 方法,该方法即表明了即使你提供了私有的构造方法,Spring 也能帮你将对象创建出来(反射的内容),看到最后的 return,很明显 BeanUtils.instantiateClass 就是通过反射的方式生成了对象。

  • 如果没有提供构造方法,则采用默认的构造方法
  • 如果提供了私有的构造方法,则设置 accessible 属性为 true,再调用反射生成对象的实例

通过以上的方式可以看出,Spring 是想尽了一切办法在帮我们正常创建一个对象。 看看传入的 DefaultBeanDefinitionDocumentReader 的声明

看到这些红框中的内容,是不是感觉到非常熟悉,这不就是我们在 applicationContext.xml 中定义的一个个标签么?原来这些东西都被 DefaultBeanDefinitionDocumentReader 写死在代码中了。

接下来我们回到 registerBeanDefinitions 这个方法的实现。

我们已经知道 createBeanDefinitionDocumentReader 是通过反射的方式生成了一个 BeanDefinitionDocumentReader 对象。下面我们看看方法的第二行做了什么事情。

getRegistry().getBeanDefinitionCount(); 先看看 getRegistry() 这个方法。这个方法基本上都不用考虑,肯定是获取到了我们传入进来的 defaultListableBeanFactory 对象。

private final BeanDefinitionRegistry registry;
@Override
public final BeanDefinitionRegistry getRegistry() {
    return this.registry;
}
复制代码

返回了成员变量 registry,那么这个 registry 在哪里赋值的呢?看看我们在示例代码中的第三步 new XMLBeanDefinitionReader() 中就知道了,在该类的构造方法中,我们赋值了 registry 这个成员变量的值。

接着看看 getBeanDefinitionCount 的实现

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public int getBeanDefinitionCount() {
    return this.beanDefinitionMap.size();
}
复制代码

就是返回了 beanDefinitionMap 这个 concurrentHashMap 的大小。该变量的定义为 Map<String, BeanDefinition> 类型,是一个以 bean 名称为 key,BeanDefinition 为 value 的 Map 对象。

结合上面的分析,那么 int countBefore = getRegistry().getBeanDefinitionCount(); 返回的实际上是未加载之前的 BeanDefinition 的数量。

接着看 registerBeanDefinitions 的第三行实现。documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 通过文档读取器开始从文档中注册 bean 的定义。 看看具体实现

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    //doc.getDocumentElement() 获取文档中的 element 元素
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {
    // 任何嵌套的 <beans> 标签在这个方法中将会导致递归
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // ... 省略部分代码
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}
复制代码

从 doRegisterBeanDefinitions 中的注释我们知道,

  • 该方法可能会导致递归,如果我们在 applicationContext.xml 配置了引用其他 <beans>
  • 该方法使用了典型的 delegate。就是我自己要做某件事,我自己不做,让其他类帮我去做。

看看方法的 preProcessXml(root),这个方法是一个空实现, 接着看看 postProcessXml(root),这个方法也是一个空实现

这是一种典型的模板方法设计模式。可以看到该方法被定义成了 protected,留作子类去实现。核心的解析逻辑在 parseBeanDefinitions 方法中。

/**
 * 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);
    }
}
复制代码

该方法解析了文档最顶层的标签元素,例如 bean,import 等等,除了解析 Spring 规定的标签节点外,还解析了自定义的标签元素。自定义的标签我们很少用到,着重看一下解析默认的标签元素。跟到 parseDefaultElement 中看看。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // IMPORT_ELEMENT = "import"
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // ALIAS_ELEMENT = "alias"
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // BEAN_ELEMENT = "bean"
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // NESTED_BEANS_ELEMENT = "beans"
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse 递归
        doRegisterBeanDefinitions(ele);
    }
}
复制代码

之前我们说到 doRegisterBeanDefinitions 方法会导致递归,在该方法的最后一行得到了验证。如果里面定义了 <beans> 类型的标签的话(嵌套 beans)

这里说明一下,早在我们介绍 loadBeanDefinitions 方法中,Spring 利用了一个 Set 集合来探测是否存在循环的 import 导入配置文件,如果出现了循环的 import 导入,Spring 会在 loadBeanDefinitions 中抛出异常。这种出现必然是有原因的,我们跟到 importBeanDefinitionResource 中看看 Spring 是如何处理 import 这种标签的。

protected void importBeanDefinitionResource(Element ele) {
    // 获取元素中的 resource 属性
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    if (!StringUtils.hasText(location)) {
        getReaderContext().error("Resource location must not be empty", ele);
        return;
    }

    // Resolve system properties: e.g. "${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

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

    // Discover whether the location is an absolute or relative URI
    boolean absoluteLocation = false;
    try {
        // 判断 resource 的值是否为绝对路径
        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?
    if (absoluteLocation) { //绝对路径
        try {
            // 调用了 loadBeanDefinitions 方法
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isTraceEnabled()) {
                logger.trace("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 relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
            // 调用了 loadBeanDefinitions 方法
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
                String baseLocation = getReaderContext().getResource().getURL().toString();
                // 调用了 loadBeanDefinitions 方法
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
        }
        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));
}

复制代码

不管 import 标签的 resource 属性配置的是绝对路径还是相对路径,我们在代码中不难发现,两个分支中都调用了 loadBeanDefinitions 这个方法。者都会导致 Spring 在解析 import 标签的同时去判断是否 import 循环的 xml 文件引用,也从侧面验证了如果循环 import 了,Spring 将会抛出异常。

对于 alias 标签的处理我们并不关心,在实际应用中这样处理少之又少,我们这里选择跳过。直接看 processBeanDefinition 这个方法的实现。

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));
    }
}
复制代码

该方法的主要作用便是从给定的 Bean Element 标签中解析出 BeanDefinition 并将其放入到给定的 registry 中,也就是我们声明的 DefaultListableBeanFactory 中。看看 delegate.parseBeanDefinitionElement 是如何解析的。

@Nullable
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<>();
    // 对别名的处理
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }
    // 将 id 作为 bean 的名称
    String beanName = id;
    // 如果 beanName 为空,则从别名的数组中取出第一个元素作为 beanName
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
    }
    // 检查名称的唯一性
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    // 解析出 bean 的定义
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        // 如果解析出的 Bean 没有 beanName,那么会自动给该 bean 生成一个名称
        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);
        // 返回一个 BeanDefinitionHolder 对象
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}
复制代码

该方法做了如下几件事

  • 获取 id 属性和别名属性以及 class 属性,如果没有名称,则将别名的第个元素作为 bean 的名称
  • 解析 BeanDefinition,返回一个 AbstractBeanDefinition 对象
  • 判断 AbstractBeanDefinition 中是否包含 bean 的名称,如果不包含,则给该 bean 生成一个 bean 的名称
  • 返回包装好的一个 BeanDefinitionHolder 对象,该对象包含了 xml 中配置的 bean 的所有属性,以及 bean 的名称和别名数组。

显然重点在第二步中,如何返回一个 AbstractBeanDefinition 对象。看看 parseBeanDefinitionElement 这个方法的实现。

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    //是否有 class 属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    // 是否有 parent 属性
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {
        // 创建一个 bean 的定义
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        // 解析 beanDefinitionAttributes 属性,包括 init-method, destroy-method 属性等等
        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;
    }
    //省略部分代码...

    return null;
}

复制代码

看看如何创建一个 BeanDefinition

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;
}
复制代码

首先通过 new 出来了一个 GenericBeanDefinition 对象,然后根据是否存在 classLoader 对象来判断是否应该给该对象设置 class 对象或者 className 名称,最后将 GenericBeanDefinition 返回。

parseBeanDefinitionAttributes 方法源码如下:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
            @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
            
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { //是否有 singleton 属性,在早前的版本存在,2.x 以后就不存在了,如果你设置了该属性,spring 会提示升级成 scope 属性
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }
    else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { //是否有 scope 属性
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }
    else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {//是否有 abstract 属性
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); //是否有 lazy-init 属性
    if (isDefaultValue(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); //是否有自动装配属性
    bd.setAutowireMode(getAutowireMode(autowire));

    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { //是否有 depends-on 属性
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }

    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if (isDefaultValue(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    }
    else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }

    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }

    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        bd.setInitMethodName(initMethodName);
    }
    else if (this.defaults.getInitMethod() != null) {
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }

    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        bd.setDestroyMethodName(destroyMethodName);
    }
    else if (this.defaults.getDestroyMethod() != null) {
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }

    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }

    return bd;
}
复制代码

其实也是很简单,就是解析 bean 标签中的其他属性,分别为 createBeanDefinition 返回的 BeanDefinition 对象的属性赋值。可能有人有疑问了,我们在 bean 标签中并没有配置其他的属性,但是部分属性还是存在默认值的。 这里的属性定义其实就是跟 applicationContext.xml 中的 bean 标签是对应上的。

另外还有其他的两个方法我们要关心一下

/**
 * Parse constructor-arg sub-elements of the given bean element.
 * 解析 bean 标签中的子元素 constructor-arg 参数
 */
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
            parseConstructorArgElement((Element) node, bd);
        }
    }
}

/**
 * Parse property sub-elements of the given bean element.
 * 解析 bean 标签中的 property 参数
 */
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
           // 其中包含对 value 的处理和对 ref 的处理
            parsePropertyElement((Element) node, bd);
        }
    }
}
复制代码

至此,我们这里便返回了一个完整的 BeanDefinitionHolder 对象。

该 BeanDefinitionHolder 中包含了从 xml 文件中解析出来的 BeanDefinition 对象和 beanName 属性。

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder) 该行就是对我们返回的 BeanDefinitionHolder 装饰一下,也就是看看是否需要添加其他额外的属性,最后返回依然是一个 BeanDefinitionHolder 对象。

最后重头戏来了,通过了 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法向 registry 中注册了一个 BeanDefinitionHolder 对象,看看是如何注册的。

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);
        }
    }
}
复制代码

重点在于我们调用了 registry 的 registerBeanDefinition 方法,registerBeanDefinition 有多个实现,而显然我们应该查看 DefaultListableBeanFactory 的实现。

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    // 判断 BeanDefinition 是否是 AbstractBeanDefinition 的实例,显然这里是的。这里是只是对 beanDefinition 做了校验
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }
    // 从 beanDefinitionMap 中获取 BeanDefinition 的定义
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 判断是否有相同名称的 bean
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // 空实现
        } else if (!beanDefinition.equals(existingDefinition)) {
            // 空实现
        } else {
            // 空实现
        }
        // 重新放入到 concurrentHashMap 中
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        if (hasBeanCreationStarted()) { //检查工厂 bean 的创建阶段是否已经开始了,创建阶段已经开始了
            synchronized (this.beanDefinitionMap) { //在这里对 beanDefinitionMap 这个 concurrentHashMap 做了同步处理,其实是为了防止并发的情况产生,导致 bean 没有注册上去。
                // 将 beanDefinition 放置到缓存中去
                this.beanDefinitionMap.put(beanName, beanDefinition);
                // 定义一个 updated 集合
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                //将所有的 beanDefinitionNames 放到 updatedDefinitions 中
                updatedDefinitions.addAll(this.beanDefinitionNames);
                // 将要添加的 beanName 放置到 updatedDefinitions
                updatedDefinitions.add(beanName);
                // 重新给 beanDefinitionNames 赋值
                this.beanDefinitionNames = updatedDefinitions;
                removeManualSingletonName(beanName);
            }
        } else {
            // 仍然在启动注册阶段
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }
    
    // 重置 BeanDefinition 的缓存
    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}
复制代码

到这里,我们所有的在 xml 中定义的 bean 对象都已经被解析出来了,所有的 bean 都被存放在 registry 中的 beanDefinitionMap 中,它是一个 concurrentHashMap,它的 key 是 beanName,value 是关于该 bean 的全部定义,其中包含 className/class, scope, init-method ... 等等属性。至此整个 bean 的加载过程也就结束了。但是注意:此时我们的 bean 并没有被创建。那么该 bean 是在什么时候被创建的呢?

Bean 的创建过程

通过上面的过程,我们可以知道以上的三行代码 Spring 从 applicationContext.xml 文件中加载了 bean 的定义,并存放到了 beanDefinitionMap 中,此时我们的 bean 对象并没有被初始化。

下面来看看 defaultListableBeanFactory.getBean 方法。看看是如何实现的。点进去我们发现 Spring 的 BeanFactory 为我们提供了各种各样的 getBean 方法。但是他们的本质都是调用了 doGetBean 方法。我们直接去看 doGetBean 方法做了什么事情。

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

199

相关文章推荐

未登录头像

暂无评论