文章目录
  1. 1. 基于配置文件方式解析BeanDefinition
  2. 2. 基于注解的方式解析BeanDefinition
  3. 3. DefaultListableBeanFactory如何注册BeanDefinition?
  4. 4. annotatedBeanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class)机理
  5. 5. 小结
  6. 6. 参考资料

接下来,我将对Spring Bean的生命周期进行较为详细的整理和总结。 本文是该部分的第一篇, 我们重点分析一下BeanDefinition元信息解析是如何进行的。

BeanDefinition主要用来描述Spring的Bean定义,它是一个接口,声明如下:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

AbstractBeanDefinition这个抽象实现实现了BeanDefinitio接口,它包含了一个Bean定义的主要属性,这里挑选几个重要的进行注释:

// Bean的Class对象
private volatile Object beanClass;
// Bean的作用域
private String scope = SCOPE_DEFAULT;
// 注入方式
private int autowireMode = AUTOWIRE_NO;
// 工厂Bean名称
private String factoryBeanName;
// 工厂Bean方法名
private String factoryMethodName;
// 属性
private MutablePropertyValues propertyValues;
// 初始化方法名
private String initMethodName;
// 销毁方法名
private String destroyMethodName;

BeanDefinition加载通常有两种方式, 基于XML/Properties配置文件的方式, 基于注解的方式。我们分别讲解

基于配置文件方式解析BeanDefinition

首先编写一个User.java类,用于测试

public class User {

    private String name;

    private int id;

省略getter setter,没什么特殊的地方, 就是一个普通的JavaBean

接着,在src/main/resources/META-INF/目录下建立bean.properties配置文件,我们在bean.properties中声明一个User对象的属性

# 使用半角括号作为占位符 参考文档 org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
user.(class)=com.snowalker.spring.bean.User
user.id=2333
user.name=踏雪无痕SnoWalker

这里注意,根据PropertiesBeanDefinitionReader的javadoc,对象的class需要通过 对象名.(class) 进行定义,否则不生效。

熟悉XML方式定义Bean的同学应当能够很轻松的理解我们的意图,定义一个name=user的对象,指明User类的全限定名,设置id=233, name=踏雪无痕SnoWalker

接着编写测试类BeanMetadataConfigurationDemo.java:

public class BeanMetadataConfigurationDemo {

    public static void main(String[] args) {

        //step0
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //step1
        PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory);

        String locations = "META-INF/bean.properties";

        //step2
        Resource resource = new ClassPathResource(locations);
        EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");

        int beanNumbers = beanDefinitionReader.loadBeanDefinitions(encodedResource);
        System.out.println("已加载的BeanDefinition数量:" + beanNumbers);

        // step3
        User user = beanFactory.getBean("user", User.class);
        System.out.println(user.toString());
    }
}

分析如下:

  • step0: 声明并初始化一个DefaultListableBeanFactory对象,DefaultListableBeanFactory的类定义如下

    public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    

    我们能够发现,DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,这就是说,它是一个Bean定义的注册中心,事实上也是BeanDefinitionRegistry接口的唯一实现。可以这么认为 : 当我们说BeanDefinitionRegistry就是指DefaultListableBeanFactory

  • step1:实例化基于Properties资源的beanDefinitionReader,它接受一个BeanDefinitionRegistry实例,根据上述的分析,我们知道这里的BeanDefinitionRegistry实例就是step0中定义的DefaultListableBeanFactory对象

  • step2:声明了bean的定义配置文件路径为classpath下的META-INF/bean.properties;我们通过ClassPathResource加载Properties资源。并通过EncodedResource包装了ClassPathResource,目的就是为了制定编码级,防止出现中文乱码
  • step3:通过beanDefinitionReader.loadBeanDefinitions(encodedResource)能够获取bean加载的数量;最终我们通过DefaultListableBeanFactory(它也是一个BeanFactory实例)的getBean获取到了name=user的User对象并将其打印出来。

运行一下这个测试类,控制台打印情况:

已加载的BeanDefinition数量:1
User{name='踏雪无痕SnoWalker', id=2333}

打印的结果证明我们成功的加载User的bean定义,并且从DefaultListableBeanFactory中获取到了User对象。

基于注解的方式解析BeanDefinition

接着我们介绍基于注解的方式解析BeanDefinition。

直接上测试用例,然后我们对其进行分析:

public class AnnotatedBeanDefinitionParsingDemo {

    public static void main(String[] args) {

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // step0
        AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);

        int beanDefinitionCounterBefore = beanFactory.getBeanDefinitionCount();

        // step1
        beanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class);
        beanDefinitionReader.register(User.class);

        int beanDefinitionCounterAfter = beanFactory.getBeanDefinitionCount();
        System.out.println("注册的总的bean数量:" + (beanDefinitionCounterAfter - beanDefinitionCounterBefore));

        // step2
        AnnotatedBeanDefinitionParsingDemo demo = beanFactory.getBean("annotatedBeanDefinitionParsingDemo",
                AnnotatedBeanDefinitionParsingDemo.class);
        System.out.println(demo);
    }
}

运行该测试,控制台打印如下:

注册的总的bean数量:2
com.snowalker.spring.lifecycle.AnnotatedBeanDefinitionParsingDemo@4cdbe50f

之所以注册总的bean数量为2,是因为在 step1 标注的位置,我们通过AnnotatedBeanDefinitionReader.register方法注册了两个bean,一个是User对象,一个是AnnotatedBeanDefinitionParsingDemo测试用例本身。

接下来,就详细的讲解一下基于注解解析BeanDefinition的逻辑:

  • step0:声明并初始化一个DefaultListableBeanFactory对象;同时声明并初始化一个AnnotatedBeanDefinitionReader对象。AnnotatedBeanDefinitionReader 是基于Java注解的BeanDefinitionReader的实现
  • step1:将AnnotatedBeanDefinitionParsingDemo、User类注册到注册当前AnnotatedBeanDefinitionReader之中。这里 注意 (非@Component标注的类也可以继续宁注册) 也就是说,AnnotatedBeanDefinitionReader中注册的可以是任意的Java类 不一定要Spring的Component及其派生注解标注
  • step2:通过annotatedBeanDefinitionParsingDemo名字获取到注册的AnnotatedBeanDefinitionParsingDemo对象,并进行打印。

主要的逻辑就这些,并不复杂。

这里我们说一下beanName是如何生成的,为何默认首字母是当前类小写?

实际上,Bean的名称来源于 BeanNameGenerator;它是一个接口,对于注解Bean的实现为 AnnotationBeanNameGenerator

我们观察一下 AnnotationBeanNameGenerator.buildDefaultBeanName(BeanDefinition definition) 这个方法。

protected String buildDefaultBeanName(BeanDefinition definition) {
    // 获取BeanDefinition中存放的bean的全限定类名
    String beanClassName = definition.getBeanClassName();
    // 判断bean全限定类名是否为null
    Assert.state(beanClassName != null, "No bean class name set");
    // 获取短类名,及去除包名后的类名,如User.java
    String shortClassName = ClassUtils.getShortName(beanClassName);
    // 返回首字母小写后的段类名
    return Introspector.decapitalize(shortClassName);
}

可以看出,实际上默认的bean名称是对全限定类名截取包名之后,将首字母进行小写处理的。

我们打开getShortName这个方法,重点看下面一行

int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
......
String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
return shortName;

PACKAGE_SEPARATOR其实就是 “.” ; 举个例子,如果类的全限定名为 a.b.c.User

则获取最后一个 “.” 所在下标, 截取 (最后一个 “.”下标 + 1, 最后一个字符下标) 就获取到了 User这个短类名。

最后调用 Introspector.decapitalize(shortClassName) 方法将首字母转换为小写。

[java.beans.Introspector.decapitalize]
public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0))){
        return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

方法中判断name如果首字母和第二个字母都是大写则原样返回,如果只有首字母是大写,则将首字母转换为小写之后返回。返回的是不同于本体的一个新的String对象。

DefaultListableBeanFactory如何注册BeanDefinition?

讲完两种方式加载beanDefinition,我们趁热打铁,聊聊 DefaultListableBeanFactory是如何注册BeanDefinition的。

从上文我们已经知道 DefaultListableBeanFactory 实际上也是一个 BeanDefinitionRegistry的唯一实现。

这让DefaultListableBeanFactory具备注册Bean定义,也就是BeanDefinition的能力。

在DefaultListableBeanFactory中,持有一个Map, 它装载注解的Bean定义。源码为:

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

根据英文注释,我们得知,beanDefinitionMap是一个存放bean定义对象的Map集合,它的key是bean名称,value是BeanDefinition对象,也就是bean的定义。

这里还要说一下另一个集合 beanDefinitionNames, 它在源码中定义如下:

/** List of bean definition names, in registration order. */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

它是一个ArrayList,根据注释以及源码逻辑分析,我们知道beanDefinitionNames这个list按照bean定义的注册顺序存放了bean的定义名称。

之所以特地拿出beanDefinitionMap和beanDefinitionNames单独说明,主要原因就是他们两个在bean注册的过程中是互相协作共同起作用的。通过beanDefinitionMap存放bean名称与BeanDefinition的映射关系,而beanDefinitionNames用于标记BeanDefinition注册的顺序。

那么他们具体是如何作用的呢?接下来就让我们在代码逻辑中一睹为快吧。

// 实现了BeanDefinitionRegistry中的registerBeanDefinition方法,也是目前默认的bean注册实现
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    // 对beanName,beanDefinition预检验
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    // 进行校验,如果校验异常则抛出
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    // 尝试通过beanName获取存在的BeanDefinition
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {

        // 如果不允许不同BeanDefinition实例使用相同beanName注册,则抛出异常,默认为允许
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }
        ......
        // 存在相同beanName的不同BeanDefinition则打印一个日志,一般是看不到了
        else if (!beanDefinition.equals(existingDefinition)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        // 使用当前的beanDefinition覆盖已有的beanDefinition(同名)
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

这里主要是将当前要注册的beanDefinition覆盖了已有的同名的beanDefinition。由于存在相同beanName处的校验只是打印了一个debug日志,所以一般来说,我们能看到的表现就是新来的BeanDefinition覆盖了原有的同名的BeanDefinition。这是允许的。

    else {
        // 如果Bean初始化已经开始,则需要加锁,否则会存在并发问题
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            // 给当前的beanDefinitionMap加锁
            synchronized (this.beanDefinitionMap) {
                // 这段代码主要就是使用新建的updatedDefinitions列表新增新的BeanDefinition名称之后,将引用指向beanDefinitionNames
                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 {
            // Bean的初始化没有开始,则同步更新beanDefinitionMap与beanDefinitionNames
            // Still in startup registration phase
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }
    ......
}

这段的核心在于判断Bean的初始化是否开始,如果开始,则需要锁定当前的beanDefinitionMap,防止并发操作beanDefinitionMap产生问题。同时更新beanDefinitionNames。将当前BeanDefinition的name添加进去。

否则说明Bean初始化未开始,则可以放心的直接更新beanDefinitionMap与beanDefinitionNames。将beanName按照注册顺序加载到beanDefinitionNames(List)中,同时将beanDefinition注册到beanDefinitionMap里。以便后续初始化bean使用。

annotatedBeanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class)机理

既然beanDefinition的注册是在DefaultListableBeanFactory中实现的,可是我们在上面的demo中并没有调用这个方法啊?

的确,我们没有直接调用DefaultListableBeanFactory.registerBeanDefinition方法,但是我们通过

beanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class)

进行了BeanDefinition的注册工作。

那么我们看一下AnnotatedBeanDefinitionReader构造方法:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
    this(registry, getOrCreateEnvironment(registry));
}

它接受一个BeanDefinitionRegistry实例,我们将初始化好的DefaultListableBeanFactory实例设置给了该构造方法(DefaultListableBeanFactory实现了BeanDefinitionRegistry接口!

接下来我们看一下AnnotatedBeanDefinitionReader.register方法实现:

public void register(Class<?>... componentClasses) {
    for (Class<?> componentClass : componentClasses) {
        registerBean(componentClass);
    }
}

可见是对componentClasses可变参进行遍历,调用registerBean(componentClass)进行注册。我们每次只注册一个bean所以只迭代一次。继续看registerBean方法:

...省略部分代码...

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

前面的校验我们先省略,重点看最后一行

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

这里才是真正的bean注册逻辑,首先定义了BeanDefinitionHolder包装了BeanName以及AnnotatedGenericBeanDefinition。我们看一下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());

    ...省略部分代码...
}

好了,真相大白,在BeanDefinitionReaderUtils.registerBeanDefinition中,通过传入的BeanDefinitionRegistry引用的registerBeanDefinition方法对BeanDefinition进行了注册。

而此处的BeanDefinitionRegistry实际上就是我们在外部初始化好的DefaultListableBeanFactory对象!

是不是有一种豁然开朗的感觉。

小结

那么我们就小结一下,在本文中,我们了解总结了Spring Bean生命周期的开始阶段:BeanDefinition元信息是如何被解析并注册的。重点学习了BeanDefinitionRegistry接口的实现DefaultListableBeanFactory类。

对DefaultListableBeanFactory类如何进行BeanDefinition注册展开了详细的分析,从而了解到,一个Spring Bean是如何开始它的生命周期。

之前笔者只关注了Bean实例化之后的过程,对Bean定义,以及定义的解析过程漠不关心,随着学习的深入,不免心生疑惑:

到底一个Bean的完整生命周期是怎样的?

因此有了这个新的子系列,我希望能够把它写完。

写毕文章,不禁有种

众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。

之感。

参考资料

极客时间-小马哥讲Spring核心编程思想



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

文章目录
  1. 1. 基于配置文件方式解析BeanDefinition
  2. 2. 基于注解的方式解析BeanDefinition
  3. 3. DefaultListableBeanFactory如何注册BeanDefinition?
  4. 4. annotatedBeanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class)机理
  5. 5. 小结
  6. 6. 参考资料
Fork me on GitHub