BeanDefinition 是定义对 Bean 的接口是 spring 容器中最重要的一个接口,spring 围绕这个接口进行对象的创建以及对象中的属性注入。
只是简单对其进行介绍其用途,但下文并不是对照本宣科的对其进行详细介绍;而是从零开始设计一套 IOC 的角度出发进行解读 BeanDefinition 结构,这样子更加对其原理以及设计思想更加了解,后续使用时就不再是陌生的;
相关 spring 源代码是 5.1.2.RELEASE 版本;
在常规开发中,如果要创建对象,有如下方式:
public class Factory{
public {static} Object<?> createBean(){
//....
}
}
public class Test {
public static void main(String [] args) throws Exception {
Constructor<Person> constructor = Person.class.getConstructor();
Person man = constructor.newInstance();
System.out.println(man);
}
public static class Person{
private String name;
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}
Supplier<T> instanceSupplier = new Supplier(){
public T get(){
//....
}
}
通过 cglib 方式进行创建增强版对象。
除 Supplier 外,工厂方法和构造方法都是通过反射调用 Method 对象进行对象创建。所以 spring 提供一个接口 InstantiationStrategy,其将调用 Method 对象进行对象创建的逻辑封装到 SimpleInstantiationStrategy 实现类里面;
那么 BeanDefinition 结构中就包含了上面的几个创建对象的内容;
public abstract class AbstractBeanDefinition {
private volatile Object beanClass;
/**
* Supplier方式
*/
private Supplier<?> instanceSupplier;
/**
* 工厂方法进行创建
*/
private String factoryBeanName;
private String factoryMethodName;
}
既然提供如上方式进行对象创建,那么意味着会有优先级;那么我们就可以设计一个简单的流程(无参数的方法),对目标类型进行实例化对象,如图所示:
在无参构建对象过程基础上,添加这么一个逻辑,【通过指定规则从中选出目标方法】,如下图;
指定规则如下:
匹配方法入参类型与入参对象类型的逻辑。如果都不匹配,意味着找不到合适的方法,直接报错;
如果有匹配的方法有2个以上,那我们就需要决策了,具体需要哪种策略了;目前有两种策略,如下:
1. 宽松策略:优先选择第一个方法;
2. 严格策略:再次精细化类型匹配,根据类型的继承层级来判断,层级越低,说明类型关系越接近,则优先选择关系越接近的方法;
ps. 具体的实现在ArgumentsHolder类
具体采用哪种策略,直接存放到 BeanDefinition 中;
public abstract class AbstractBeanDefinition {
/**
* true代表是采用严格策略;
* false代表是采用宽松策略
*/
private boolean lenientConstructorResolution = true;
}
上面的过程中,通过反射来获取的方法列表,这里有两个可能性,是只获取 public 方法,还是获取所有的方法;
public abstract class AbstractBeanDefinition {
/**
* true代表获取所有方法;
* false代表是只获取public方法
*/
private boolean nonPublicAccessAllowed = true;
}
接下来介绍如何将 ConstructorArgumentValues 解析成 ArgumentsHolder 对象的;
接下来对入参解析进行介绍,
public abstract class AbstractBeanDefinition {
/**
* 构建对象的入参对象
*/
private ConstructorArgumentValues constructorArgumentValues;
}
public class ConstructorArgumentValues {
private Map<Integer/*参数位置*/, ValueHolder/*参数对象*/> indexedArgumentValues;
private List<ValueHolder> genericArgumentValues;
}
public class ValueHolder{
/**
* 原始值对象
*/
private Object value;
/**
* 是否已经转换过了,也就是意味着是否已经解析过了
*/
private boolean converted = false;
/**
* 解析后的对象
*/
private Object convertedValue;
}
public class ArgumentsHolder {
public final Object[] arguments;
}
转换的流程如下:
阶段一:旧ConstructorArgumentValues对象转成新ConstructorArgumentValues对象
遍历旧ConstructorArgumentValues中的ValueHolder对象,经过spring解析得到新的ValueHolder对象
PS. 主要的实现代码BeanDefinitionValueResolver.resolveValueIfNecessary方法中。
值得注意的是,这里有用到EL表达式;所以,一些灵活得到入参对象的,可以通过EL表达式来抉择;
阶段二:新ConstructorArgumentValues对象解析成ArgumentsHolder
遍历方法的入参数组,通过位置角标获取对应的ValueHolder对象;
如果获取不到,则尝试在genericArgumentValues对象中查找;
找到的话,会根据是否已经转换(converted为true),来选择对于的属性值;
如果converted为true,则选择convertedValue属性对象
如果converted为false,则选择value经过TypeConverter类型转换类进行转换得到的对象;
经过 spring 创建的对象,并不会只有一次,所以为了提高第二次的创建对象,设计了缓存机制;
其缓存机制主要是为了减少查找目标构建对象的方法;至于入参是否有必要在解析,是根据 ConstructorArgumentValues 对象的 ValueHolder 对象中的 converted 是否为 false,只要有一个为 false,那就意味着有必要进行解析;
public class RootBeanDefinition {
/**
* 为了避免并发实例同一个对象,需要一个锁来解决并发问题
*/
final Object constructorArgumentLock = new Object();
/**
* 存放第一次构建对象的方法
*/
Executable resolvedConstructorOrFactoryMethod;
/**
* 第一次解析后,就会设置为true
*/
boolean constructorArgumentsResolved = false;
/**
* 存放解析后的入参对象
*/
Object[] resolvedConstructorArguments;
/**
* 存放未解析的入参对象
*/
Object[] preparedConstructorArguments;
/**
* 只缓存工厂方法,如果构建是通过工厂方法方式,
* 那么该属性与resolvedConstructorOrFactoryMethod是同一个对象
*/
volatile Method factoryMethodToIntrospect;
}
在有入参构建对象的逻辑图基础在添加缓存机制,如下图:
为了处理是否有必要解析入参这个场景,就需要在 ValueHolder、ArgumentsHolder 对象添加额外的属性,来存放解析前的对象;
public class ValueHolder{
/**
* 原始值对象
*/
private Object value;
/**
* 是否已经转换过了,也就是意味着是否已经解析过了
*/
private boolean converted = false;
/**
* 解析后的对象
*/
private Object convertedValue;
/**
* 原始入参对象
*/
private Object source;
}
public class ArgumentsHolder {
/**
* 原生对象
*/
public final Object[] arguments;
/**
* 解析后的入参对象集合
*/
public final Object[] rawArguments;
/**
* 未解析的入参对象
*/
public final Object[] preparedArguments;
/**
* 只要有一个ValueHolder对象的converted属性为true,resolveNecessary只就会设置为true
*/
public boolean resolveNecessary = false;
}
得到 ArgumentsHolder 后,会将其存放到 RootBeanDefinition 对象的属性中;
上面介绍了有 BeanDefinition 结构中入参,但还有另外的一个场景,那就是由应用程序传过来的入参,这里简称【应用入参】;针对该场景,我们就不需要采取缓存机制了;所以在上面的流程基础上添加【应用入参】的逻辑,如图所示:
当需要指定方法需要做增强操作时,就需要以代理对象创建的形式;
public abstract class AbstractBeanDefinition {
/**
* 需要覆盖当前类中的方法
*/
private MethodOverrides methodOverrides;
}
这块逻辑的实现,可以查阅《spring 的 IOC 使用以及原理》中有关 Lookup 注解的使用;
既然创建出对象了,那么就需要考虑这个对象的所影响区域,也可以理解为对创建对象这个动作进行影响;所以,需要增加一个属性来记录作用域信息;
public abstract class AbstractBeanDefinition {
/**
* 作用域,这里有默认实现的几种区域;
* 1. "" 或 singleton =》 单例
* 2. prototype =》 原型
* 3. 其他作用域
*/
private String scope = SCOPE_DEFAULT;
}
这里补充一下作用域的概念,网上也有,这里就简单的讲一下;
在创建对象过程中,对象中的属性是依赖对象,我这里将其定义为【后期依赖对象】,即创建对象后,再去创建属性对应的对象;然而有特殊的场景,就是创建当前对象前,先创建其他对象,我这里将其定义为【前期依赖对象】;之所以要有【前期依赖对象】,我自己理解为:提前发现问题,减少不必要的初始化动作;例如要创建对象 A,依赖对象的有 B、C 等对象,当创建 C 对象时会发生错误异常;如果先创建了 A 对象,再去创建 C 对象,那么创建 A 对象这个动作是没有必要做的;所以,这个【前期依赖对象】起到了校验效果;
public abstract class AbstractBeanDefinition {
/**
* 里面的元素是对象名称
*/
private String[] dependsOn;
}
当对象创建后,接下来就会对该对象中的属性进行填充;
public abstract class AbstractBeanDefinition {
/**
* 自动注入模式
* 0(AUTOWIRE_NO) => 手动注入模式
* 1(AUTOWIRE_BY_NAME)=》根据属性名自动注入模式
* 2(AUTOWIRE_BY_TYPE)=》根据属性类型自动注入模式
* 3(AUTOWIRE_CONSTRUCTOR)=》根据构造方式自动注入模式
* 4(AUTOWIRE_AUTODETECT)=》自动检测模式,根据目标类的构造函数中是否有入参;
* 如果没有参数,则使用 AUTOWIRE_BY_TYPE ;否则使用 AUTOWIRE_CONSTRUCTOR
*/
private int autowireMode = AUTOWIRE_NO;
}
在属性注入环节中使用该模式的常见,流程如下:
上面的流程图中,只涉及三种模式,其余的两种模式,会在哪些场景下使用呢?
public abstract class AbstractBeanDefinition {
/**
* 对对象的属性进行注入所存放的对象
*/
private MutablePropertyValues propertyValues;
}
public class MutablePropertyValues{
/**
* 进行
*/
private final List<PropertyValue> propertyValueList;
/**
* 已经处理过的属性名
*/
private Set<String> processedProperties;
/**
* 是否已经进行转换过
*/
private volatile boolean converted = false;
}
其流程如下:
在 spring 容器有提供 hook,也就是我们可以通过插件的方式实现属性注入;流程如下:
从上面的场景来看,不难梳理属性注入的优先级:Hook > PropertiesValues > AutoMode;
一旦有优先使用 PropertiesValues 的方式的场景,我们直接在属性声明设置绕过 Hook 的相关代码,例如 Autowired 注解,我们可以不使用该注解即可;
是否有不去掉 @Autowired 注解,也可以优先 PropertiesValues 属性注入呢?是有的,我们需要一个属性来记录哪些属性不需要通过 Hook 来进行属性注入。
public class RootBeanDefinition {
/**
* 创建对象后,调用前置Hook进行处理,为了避免并发问题,需要加锁
*/
final Object postProcessingLock = new Object();
/**
* true表明已经调用前置Hook进行处理过了,无需再次调用;
*/
boolean postProcessed = false;
/**
* 由外部程序所管理的属性列表;这里的外部程序主要指的Hook插件
*/
private Set<Member> externallyManagedConfigMembers;
}
只要我们提前将相关的属性保存到【externallyManagedConfigMembers】对象中去,那么意味其他 Hook 插件程序无权对其属性进行操作;只有对应的 Hook 有权使用;
该【externallyManagedConfigMembers】对象的元素注入,是在创建对象后,属性注入前调用处理的;是调用下面的实现类来处理;
public interface MergedBeanDefinitionPostProcessor
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
Class<?> beanType, String beanName);
}
当对象的属性注入结束后,需要检测是否有遗漏的属性未注入的;这里有四种策略,来决定属性依赖检测的方式;
public abstract class AbstractBeanDefinition {
/**
* 属性依赖检查策略
* 0(DEPENDENCY_CHECK_NONE)不需要检查
* 1(DEPENDENCY_CHECK_OBJECTS)只检查应用类型
* 2(DEPENDENCY_CHECK_SIMPLE)只检查基础类型
* 3(DEPENDENCY_CHECK_ALL)检查所有
*/
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
}
一般来说,需要一块内存区域记录哪些属性已经初始化的,然后遍历目标类的属性列表,来检查是否有属性未初始化的;然而在 spring 中,并没有这个内存区域来记录这些信息;这块的逻辑,个人理解是有问题的;所幸的是,spring 并没有提供类似注解的形式去修改该属性,所以一般都不会触发属性依赖检测;
当对象的属性注入后,将会触发初始化动作;一般来说,实例化动作指的是创建对象;而初始化动作指的调用对象的指定的方法;这里只讲应用程序执行指定的方法,不包含框架自身所提供的方法;
当对象被销毁时,对应的调用对象的销毁方法;
public abstract class AbstractBeanDefinition {
/**
* 是否强制调用init方法;这一块只是呈现了校验动作;
* 也就是说当其值为true时,init方法必须存在,否则报错
*/
private boolean enforceInitMethod = true;
/**
* 与enforceInitMethod的处理逻辑相似
*/
private boolean enforceDestroyMethod = true;
/**
* 初始化调用的方法
*/
private String initMethodName;
/**
* 销毁对象所调用的方法
*/
private String destroyMethodName;
}
这里的 init 方法,是由 Hook 插件自行去调用的;
public class RootBeanDefinition {
/**
* 由外部程序所管理的init方法列表;这里的外部程序主要指的Hook插件
*/
private Set<String> externallyManagedInitMethods;
/**
* 由外部程序所管理的销毁方法列表;这里的外部程序主要指的Hook插件
*/
private Set<String> externallyManagedDestroyMethods;
}
这个逻辑,跟 3.3.1 节一样,这里不再阐述;
有关具体的属性对象检索逻辑,可以查阅《spring 篇之属性注入》,这里只阐述相关的属性值描述
从这篇文章中得到过滤目标对象的大体过程是:判断是否是候选人名单 > 判断泛型类型是否匹配的 > 判断限定是否匹配的;
public abstract class AbstractBeanDefinition {
/**
* 是否将该实例对象作为候选者;
*/
private boolean autowireCandidate = true;
}
public class RootBeanDefinition {
/**
* 当做解析后的目标对象的泛型类型
*/
volatile ResolvableType targetType;
/**
* 缓存解析后的目标对象的class类型
*/
volatile Class<?> resolvedTargetType;
volatile Method factoryMethodToIntrospect;
}
有关泛型校验逻辑较为复杂,会以另一篇文章进行介绍,这里不再阐述;
public abstract class AbstractBeanDefinition {
/**
* 限定注解对象集合
*/
private final Map<String, AutowireCandidateQualifier> qualifiers;
}
public class RootBeanDefinition{
/**
* 限定注解
*/
private AnnotatedElement qualifiedElement;
}
上面这两个属性,在常规的应用程序是很少直接使用的;而是直接在类、属性、方法等声明处,表明限定注解;由于没有看过 spring-test 框架源代码,所以只能猜想该 TEST 框架有可能会使用其操作;
当经过上面层层的过滤,仍然有多个依赖对象时,需要一个策略,来选择最合适的对象;其中优先级最高的就是这个 primary 属性;
public abstract class AbstractBeanDefinition {
/**
* 是否将该实例对象作为主要的候选者;
* 我们应该谨慎使用该属性,一旦出现两个实例对象都是primary的话,程序就会抛出异常;
*/
private boolean primary = false;
}
public abstract class AbstractBeanDefinition {
/**
* 如果为true时,在spring容器启动,就会去创建对象
* 如果为false时,在使用时,才会触发对象创建
*/
private boolean lazyInit = false;
}
有关 BeanDefinition 结构中大部分的定义都介绍了,只剩下小部分,都是一些特殊特殊场景使用;有后续有使用该场景时,再进行解读;
一个 IOC 框架为围绕对象的构建、属性注入、对象初始化、对象销毁整个环节进行的。上面只是罗列了关键的逻辑,至于一些特殊场景,并没有考虑在内;例如 Hook 机制,其会影响对象创建过程,甚至会改变;
如果结合 Hook 逻辑,那么其就会变得及其复杂,很难解读;
可以查阅我早期发表的文章,在看其文章时,最好是集合源代码阅读;
领取专属 10元无门槛券
私享最新 技术干货