跟我学Spring之Bean生命周期-BeanDefinition元信息解析
接下来,我将对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的完整生命周期是怎样的?
因此有了这个新的子系列,我希望能够把它写完。
写毕文章,不禁有种
众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。
之感。
参考资料
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。