读懂Spring的必经之路—BeanDefinition
最近有人问我:我想学习spring源码,可是源码量那么大,我应该从哪里开始学?有没有哪个切入点啊,应该从哪里开始看啊?我想了想,决定写一篇博客,说说自己个人的看法,希望能对想学spring源码但又不知道从何开始的人有点帮助。
从那开始看?
对于异常庞大的Spring源码,可能很多人在一开始就望而生畏,不知道应该怎么去阅读,从哪开始阅读。
如果我们将Spring比喻成一个奶茶店,一个个的bean比喻成一杯杯的奶茶。那在我们要做一杯奶茶之前,我们最应该知道什么—这杯奶茶的信息
波霸奶茶,去冰无糖,走奶,不要茶
只有在店员知道了你对这杯奶茶的需求的描述之后,才能给你做一杯你想要的奶茶
Spring中如何定义Bean的信息
在spring中,处处都离不开Bean。跟上面例子同样的道理,如果我们要知道Spring怎么生成一个Bean,我觉得应该从Bean的信息开始看起—BeanDefinition
我们会发现,在Spring中,如果我们在一个类上面加了@Lazy
注解,这个类就会变成懒加载模式(即不会在容器初始化的时候马上生成bean,而是在第一次获取bean的时候才生成bean)
如果我们在类上面加了@DependsOn("b")
注解,那么这个类就会在b这个bean实例化之后才进行实例化
而这,也就是我们对于我们需要创建的bean的描述,就像我们描述我们需要的奶茶一样!
BeanDefinition
在Spring中,BeanDefnition是一个接口,他继承了两个接口,并且提供了许多实现类供Spring自己和程序员使用
BeanDefinition继承的两个类
- BeanMetadataElement:
public interface BeanMetadataElement {
//用来获取这个类所在的路径
@Nullable
Object getSource();
}
- AttributeAccessor:
这个接口提供了5个方法,所有方法都与Attribute有关。这只是一个接口,在具体实现类中,会维护一个Map用于保存Bean的一些额外信息,程序员也可以通过提供的api往某个BeanDefinition中存入自己想要进行储存的信息。
public interface AttributeAccessor {
void setAttribute(String name, @Nullable Object value);
Object getAttribute(String name);
Object removeAttribute(String name);
boolean hasAttribute(String name);
String[] attributeNames();
}
可能的应用场景
对于第一个接口BeanMetadataElement
可能会比较容易理解,在具体实现类中就是用来获取类所在路径的
但是对于第二个可能如果对于第一次看到的话,会想不明白这个接口提供的这些方法有什么用?在这里我简单举一个例子:
对于我们自己手动注册的BeanDefinition(后面文章会讲到怎么自己手动注册bd),我们可以调用 setAttribute(“zes”, true) 方法,为里面加入一个标识符,在我们下次需要判断这个BeanDefinition是不是我们自己注册的时候,只需要通过 getAttribute(“zes”) 来判断是否为 true 就可以了
实际的应用场景
在Spring的ConfigurationClassUtils#checkConfigurationClassCandidate()中有这么一段
//确定是不是一个全配置类 判断是否加了 @Configuration
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
在Spring中通过这种方式来判断这个bd到底是不是一个配置类,也就是在这里判断配置类到底有没有加 @Configuration 注解,如果加了这个注解,则这个配置类最终将通过cglib进行动态代理!
BeanDefinition除了上面的两个接口之外,像上面UML图里面的,还有许多的实现类,这里会讲一下几个实现类
- RootBeanDefinition:
这个实现类经常被用作源码当中的载体,在对bd的封装和信息的处理过程中,大多数都使用了RootBeanDefinition作为载体。在几乎所有的Spring的内部类,都是使用了RootBeanDefinition作为载体将数据进行封装。 - ChildBeanDefinition:
这个实现类曾经是和RootBeanDefinition
相对立的存在,在之前如果一个BeanDefinition设置了父BeanDefinition之后,子bd将会使用ChildBeanDefinition来装在类信息。而在现在,GenericBeanDefinition已经可以代替ChildBeanDefinition了 - GenericBeanDefinition:
见名知意,这是一个通用的bd,在我们自己写得类当中,几乎绝大多数的类,都是使用这个来封装类信息的!
在Spring中还有许多的bd没有讲(详细可参见上面的UML类图),但是只要理解了这三个bd,其他的通过名字都可以很好的理解其作用和含义。
bd在源码当中的使用
在源码中有这样一个判断
//这三个方法都是调用BeanDefinition中的API
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
......
}
1. 判断这个类是不是抽象类
2. 判断这个类是不是单例的
3. 判断这个类需不需要懒加载
这只是BeanDefinition在源码中的冰山一角,源码中还有许多地方通过bd来获取类的信息,来实现对类更加丰富的实例化过程
可能会有一些人觉得,这些东西都不重要,更想知道Spring是怎么实例化一个bean
但是如果这些基础都不打好的话,想读懂Spring只能是囫囵吞枣
我虽然接触Java可能时间并不长,但是对于Spring源码前前后后读了三次,也看了很多书籍
但是之前的两次,都让我有一种我只读懂了代码的感觉,甚至有一些代码都读不懂!
所以在我踩了很多坑之后,想写一篇文章,可以让后来的人学得没那么费力。