spring源码系列一--BeanDefinition

如果说java是由对象组成,那么spring-framework框架可以说是由BeanDefinition所构成。BeanDefinitiion其实是spring中的顶级接口,我们在阅读源码之前必须要先搞懂BeanDefinition的作用以及成员变量的含义和其不同的实现类在spring中所扮演的角色。本文会详细解释spring初始化阶段所用到的BeanDefinition的实现类以及相应成员变量的含义。

BeanDefinition

  • BeanDefiniton与对象的关系
  • BeanDefinition的重要属性与BeanDefinition的接口
    • BeanDefinition接口:
    • 与Spring中bean的生命周期有关的属性和方法
    • BeanDefinition继承的接口
  • BeanDefinition的相关实现类

BeanDefiniton与对象的关系

总所周知spring是一个管理bean的容器,那么在此有必要解释java中的对象和spring中的bean这两者的联系;我们的java文件在编译以后会生成class文件,jvm启动的时候会将classpath目录下的class文件加载到方法区,当项目中需要实例化某个类时就会根据类的符号引用找到方法区中的类文件元数据,也就是如下图中的User类中我们定义的id、name等等字段的类文件,此时可以将符号引用转换成实际引用创建对象以后保存在堆上(当然从操作系统的角度来看,其实还是虚拟地址不是真实的物理地址)。
spring源码系列一--BeanDefinition_第1张图片
java中所谓的对象,其实是对我们业务过程的一种抽象,而有了对象的存在也能够更方便得管理我们的业务过程,那么既然spring是用来管理java中的对象的,bean的含义其实也不言而喻,就是对我们对象的一种抽象,试想一下脱离spring框架,如果需要我们自己实现一个对于对象管理的工具要如何实现?首先肯定是需要一个类能够描述所有的业务对象,比如我们可以自己设计一个类叫做GeneralObject,伪代码如下:

public class GeneralObject {
	private class sourceObject;
	private String objectName;
	private File classFile;
	
}

我们可以很自然得想到,要设计一个通用的类能够描述所以的业务类,那么肯定会需要所描述的类的元数据、实例化后对象的名字、类文件的地址等等。在spring框架中BeanDefinition就充当了这个角色,只不过spring框架比较强大,除了上述这些属性还增加了许多其他的属性,比如管理bean的实例化时机的属性scope等等,下面将让我们对BeanDefinition这个顶级接口具体有哪些能力来一探究竟。

BeanDefinition的重要属性与BeanDefinition的接口

BeanDefinition接口:

package org.springframework.beans.factory.config;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
	
	int ROLE_APPLICATION = 0;
	
	int ROLE_SUPPORT = 1;
	
	int ROLE_INFRASTRUCTURE = 2;
	
	void setParentName(@Nullable String parentName);
	
	@Nullable
	String getParentName();
	
	void setBeanClassName(@Nullable String beanClassName);
	
	@Nullable
	String getBeanClassName();
	
	void setScope(@Nullable String scope);
	
	@Nullable
	String getScope();
	
	void setLazyInit(boolean lazyInit);
	
	boolean isLazyInit();
	
	void setDependsOn(@Nullable String... dependsOn);
	
	@Nullable
	String[] getDependsOn();
	
	void setAutowireCandidate(boolean autowireCandidate);
	
	boolean isAutowireCandidate();
	
	boolean isPrimary();
	
	void setFactoryBeanName(@Nullable String factoryBeanName);
	
	@Nullable
	String getFactoryBeanName();
	
	void setFactoryMethodName(@Nullable String factoryMethodName);
	
	@Nullable
	String getFactoryMethodName();
	
	ConstructorArgumentValues getConstructorArgumentValues();
	
	default boolean hasConstructorArgumentValues() {
		return !getConstructorArgumentValues().isEmpty();
	}
	
	MutablePropertyValues getPropertyValues();

	default boolean hasPropertyValues() {
		return !getPropertyValues().isEmpty();
	}

	void setInitMethodName(@Nullable String initMethodName);

	@Nullable
	String getInitMethodName();

	void setDestroyMethodName(@Nullable String destroyMethodName);

	@Nullable
	String getDestroyMethodName();

	void setRole(int role);

	int getRole();

	void setDescription(@Nullable String description);

	@Nullable
	String getDescription();

	boolean isSingleton();

	boolean isPrototype();

	boolean isAbstract();

	@Nullable
	String getResourceDescription();

	@Nullable
	BeanDefinition getOriginatingBeanDefinition();

}

与Spring中bean的生命周期有关的属性和方法

String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;

String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
//这两个很好理解,就是在Spring的bean工厂中定义的两个字符串常量
//SCOPE_SINGLETON的值为singleton,SCOPE_PROTOTYPE的值为prototype

boolean isSingleton();

boolean isPrototype();

以上两个常量很好理解,这里说明一下singleton与prototype的区别。
singleton:单例bean,如果一个bean是单例的,那么在它被实例化以后就会被存放在DefaultListableBeanFactory类的singletonObjects(这也是很多博客中提到的,spring的单例池)map中。
prototype:原型,每次需要bean的时候都会重新实例化一个,也就是每次都需要经过完整的bean生命周期。
单例和原型的处理方式有所不同,在spring源码中几乎处处都有体现,因为针对原型如果每次都要解析需要注入的属性、构造这个类的对象所需要使用哪一个构造方法等操作效率会很低,因此spring对此做了优化,通过缓存的机制,这个等分析到具体代码的时候再详细阐述。

void setScope(@Nullable String scope);

@Nullable
String getScope();
//与scope相关的api
  • 懒加载
void setLazyInit(boolean lazyInit);

boolean isLazyInit();

配置懒加载的bean,不会在Spring初始化的时候实例化bean,而是要等到使用这个bean的时候才会去初始化。

  • DependsOn
void setDependsOn(@Nullable String... dependsOn);

@Nullable
String[] getDependsOn();

某过有一个UserService的bean,这个bean的初始化需要依赖与PowerService、RoleService那么可以在UserService上声明@DependsOn标签,参数是String数组,传入PowerService、RoleService的BeanName即可,那么Spring在初始化UserService的时候会去判断所依赖的bean是否已经实例化完成了。

  • 生命周期回调方法
void setInitMethodName(@Nullable String initMethodName);

@Nullable
String getInitMethodName();

void setDestroyMethodName(@Nullable String destroyMethodName);

@Nullable
String getDestroyMethodName();

实现生命周期回调方法,可以有两种方式:

  1. 在xml中配置init-method="xxx"或者在某个方法上声明@PostConstruct,但是这两者Spring在处理的时候有些区别,通过xml配置底层是通过BeanDefinition的setInitMethodName方法,然后在某个后置处理器调用的时候会调用方法,而@PostConstruct注解声明的方法,并不会去调用setInitMethodName方法,都是在某个后置处理器解析到方法的时候就会直接调用。
  2. 直接为bean的BeanDefinition赋值
    在这里插入图片描述
    3.实现InitializingBean,重写afterPropertiesSet方法

在createBean的initializeBean方法中会执行生命周期回调方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以上只是简单得列举调用回调方法的时机,具体代码等将Bean生命周期的时候再来详细阐述。

  • 接口的多个实现的处理
void setAutowireCandidate(boolean autowireCandidate);

boolean isAutowireCandidate();

boolean isPrimary();

假设我们有一个UserService接口,其中有两个它的实现类,比如有UserServiceImpl与UserServiceWrapperImpl,我们有一个UserController,需要注入UserService

public class UserController {
	@Autowired
	private UserService userService;
}

在这种情况下如果UserController的自动装配模型是byType,那么首先会根据UserService类型去Spring单例池中找对应的BeanName,这个时候就可以找出多个了,然后再根据寻找出的beanName再去获取bean,这个时候如果beanName没有与注入属性的属性名一致那么就无法正常注入,这个时候Primary与AutowireCandidate属性就可以实现注入了,前者是将注解声明在属性上,参数是beanName,语义是该属性注入遇到多个实现类的时候会选择注解中的beanName对应的bean作为要注入的bean。后者是配置在实现类中,语义是当前这个bean不作为注入的候选bean参与注入。

  • 构造方法参数与成员变量属性集合
ConstructorArgumentValues getConstructorArgumentValues();

default boolean hasConstructorArgumentValues() {
	return !getConstructorArgumentValues().isEmpty();
}

MutablePropertyValues getPropertyValues();

default boolean hasPropertyValues() {
	return !getPropertyValues().isEmpty();
}

这两个属性在xml中都有对应的配置,但是在注解方式中笔者翻阅了spring的doc文档,并没有找到相应的注解方式赋值(可能是xml这种方式注入bean元数据的时代淘汰物),这里给个通过BeanDefinition的方式。
在这里插入图片描述

  • BeanClassName
void setBeanClassName(@Nullable String beanClassName);

@Nullable
String getBeanClassName();

理解了bean与BeanDefinition与我们pojo实体类的关系,这两个方法就很容易理解了,保存的时类的全路径名。

  • ParentBeanDefinition
void setParentName(@Nullable String parentName);

@Nullable
String getParentName();

boolean isAbstract();

BeanDefinition可以设置一个父BeanDefinition,子BeanDefinition可以继承父BeanDefinition的所有属性。
spring源码系列一--BeanDefinition_第2张图片
在这里插入图片描述
可以看到最后打印出来的type属性值是parent-type,关于这里还需要说明的一点是isAbstract,因为前面我们说了,BeanDefinition的作用就是描述一个Bean,最后要实例化一个bean,那么为什么还要有是不是抽象的这么一个属性呢?原因就在于父子BeanDefinition的存在,试想一个场景,如果说某一类BeanDefinition的属性值很多都是相同的,那么我们就可以抽取一个抽象的父BeanDefinition,这个父BeanDefinition不需要被实例化,只是将属性值赋值给每个子BeanDefinition,当然这种用法目前应该是比较少见了。Spring在实例化之前会调用merge方法,会将子bd的父bd属性值合并下来(如果存在父bd)。

  • 工厂方法
void setFactoryBeanName(@Nullable String factoryBeanName);

@Nullable
String getFactoryBeanName();

void setFactoryMethodName(@Nullable String factoryMethodName);

@Nullable
String getFactoryMethodName();

Spring设计了这个FactoryBeanName与FactoryBeanMethod很容易理解,因为我们说过BeanDefinition的目的就是描述Bean、实例化Bean的,那么有工厂方法自然也是很正常的,如果某个bean的实例化过程比较复杂,通过工厂方法会使代码健壮性大大提高,比如实例化DataSource的时候。

  • 其他
//可以在一个类上声明Description注解,里面可以传入对这个bean的描述,用处不大
void setDescription(@Nullable String description);

@Nullable
String getDescription();

//用来描述当前bean的class文件的路径
@Nullable
String getResourceDescription();

resourceDescription的打印信息
在这里插入图片描述
当然在AbstractBeanDefinition中还新增加了一些属性,这些等到源码分析到的时候再聊。除了这些成员变量和方法我们还可以BeanDefinition接口还继承了两个接口AttributeAccessor和BeanMetadataElement。

BeanDefinition继承的接口

public interface AttributeAccessor {

	void setAttribute(String name, @Nullable Object value);

	@Nullable
	Object getAttribute(String name);

	@Nullable
	Object removeAttribute(String name);

	boolean hasAttribute(String name);

	String[] attributeNames();

}

要说明AttributeAccessor接口的使用,要先举一个老生常谈的例子,spring的配置类如何正确使用?

@Configuration
@ComponentScan("com")
public class AopConfig {

	
	@Bean
	public CycleA cycleA() {
		return new CycleA();
	}
	
	@Bean
	public CycleB cycleB() {
		CycleA cycleA = cycleA();
		//do something with cycleA ...
		return new CycleB();
	}
}

@Component
@ComponentScan("com")
public class AopConfig {

	
	@Bean
	public CycleA cycleA() {
		return new CycleA();
	}
	
	@Bean
	public CycleB cycleB() {
		CycleA cycleA = cycleA();
		//do something with cycleA ...
		return new CycleB();
	}
}

以上两段代码,在spring中作为配置类都可以正常启动,不会报错,但是这里有两个个问题,为什么我们一个配置类要加@Configuration注解,为什么@Component在spring中也可以正常使用?其次,假设生成CycleB对象的时候需要使用到CycleA对象,上述这种方式是否破坏了spring Bean单例的原则,因为我们分明手动调用了实例化CycleA对象的方法。这个问题笔者之前工作过程中P7的多年开发工程师也对此感到不解,那么我们可以看一下这两者不同的注解spring是如何处理的。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println(context.getBean(AppConfig.class));

打印配置类的元数据可以发现,第一种方式打印的是

com.config.AppConfig$$EnhancerBySpringCGLIB$$33f14ba8@588df31b

而第二种方式打印的是

com.config.AppConfig@6ee52dcd

这里可能你会有个疑问,对于一个加了@Configuration注解的配置类,spring为什么要去通过cglib进行代理,而不同的注解生成的配置类对象不同又是如何做到的?
其中的奥秘就在于上述的AttributeAccessor接口中,针对@Configuration注解的类,spring在解析的时候会在attributs属性中(attributes最后的实现其实就是LinkedHashMap,用来存放属性名-属性值的键值对)增加一个描述标识该类是一个配置类。这个map的作用也是用来描述一个bean的,它所描述的是除了BeanDefinition以外的信息,比如一个类是不是配置类。
在这里插入图片描述
至于为什么要对配置类进行代理,以及是否破坏spring单例原则的问题,笔者在后续的spring源码解读中会对此展开重点讲解,在此先跳过这个问题。

BeanMetadataElement实现类比较简单,就提供了一个方法,可以获取到当前bean的class文件对象,与BeanDefinition的getResourceDescription不同,这不是一个字符串而是一个FileSystemResource对象,提供了方法可以去读写这个class文件。
然而这两个接口有一个共同的实现类BeanMetadataAttributeAccessor

public interface BeanMetadataElement {

	Object getSource();

}

最后列举一下BeanDefinition接口的关系图:
spring源码系列一--BeanDefinition_第3张图片

BeanDefinition的相关实现类

  • RootBeanDefinition与ChildBeanDefinition
    RootBeanDefinition有两个作用,用来描述某个Bean注册到BeanDefinitionMap当中,另一个作用就是当作父bd,用来做合并,合并的时候会实例化一个新的RootBeanDefinition,父bd属性赋值给新实例化的rdb,父bd调用set方法,将子bd的属性赋值给自己。
    在这里插入图片描述
    现在RootBeanDefinition主要是在调用合并方法的时候会用到。
  • GenericBeanDefinition
    这个BeanDefinition已经可以完全取代ChildBeanDefinition

GenericBeanDefinition is a one-stop shop for standard bean definition purposes.

  • Like any bean definition, it allows for specifying a class plus optionally
  • constructor argument values and property values. Additionally, deriving from a parent bean definition can be flexibly configured through the “parentName” property.

以上的java doc是在GenericBeanDefinition中的作者注释,GenericBeanDefinition并没有实现特别的功能,作者也说了这个BeanDefinition是一个用来定义标准Bean的,而且可以设置父bd,也就是说它本身可以将自己这个类的实例对象作为父bd,那么也就完全可以取代ChildBeanDefinition。

package org.springframework.beans.factory.support;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

@SuppressWarnings("serial")
public class GenericBeanDefinition extends AbstractBeanDefinition {

	@Nullable
	private String parentName;


	public GenericBeanDefinition() {
		super();
	}


	public GenericBeanDefinition(BeanDefinition original) {
		super(original);
	}


	@Override
	public void setParentName(@Nullable String parentName) {
		this.parentName = parentName;
	}

	@Override
	@Nullable
	public String getParentName() {
		return this.parentName;
	}


	@Override
	public AbstractBeanDefinition cloneBeanDefinition() {
		return new GenericBeanDefinition(this);
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof GenericBeanDefinition)) {
			return false;
		}
		GenericBeanDefinition that = (GenericBeanDefinition) other;
		return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other));
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("Generic bean");
		if (this.parentName != null) {
			sb.append(" with parent '").append(this.parentName).append("'");
		}
		sb.append(": ").append(super.toString());
		return sb.toString();
	}

}

继承自AbstractBeanDefinition,我们可以看到并没有实现特别的功能。

  • ConfigurationClassBeanDefinition
    在java-config配置类中通过@Bean加入到spring中的bean都是这个类来定义属性,这种bean内部没有存beanClasss属性,而是保存了beanMetho。
    spring源码系列一--BeanDefinition_第4张图片
    在这里插入图片描述
    在这里插入图片描述
    我们来看看打印beanClass是什么结果:
    在这里插入图片描述
    在这里插入图片描述

说明ConfigurationClassBeanDefinition并没有报错beanClass。可以看到ConfigurationClassBeanDefinition是一个内部类,ConfigurationClassBeanDefinitionReader是Spring中特别重要的一个类,在讲Spring初始化的时候我们会提到,而ConfigurationClassBeanDefinition多了两个属性
spring源码系列一--BeanDefinition_第5张图片
spring源码系列一--BeanDefinition_第6张图片
可以看到ConfigurationClassBeanDefinitio中保存的两个属性,第一个是保存这个bean的方法所在的那个类,以及该类的注解等信息,第二属性是生成该类的方法。看到这里我们也可以想到为什么不需要保存beanClass,因为这个bean是由一个声明了@Bean的方法所实例化的。

  • AnnotatedGenericBeanDefinition
    通过register方法注册的类都是通过这个类型的bean来描述的
    spring源码系列一--BeanDefinition_第7张图片
    在这里插入图片描述

  • ScannedGenericBeanDefinition
    如果一个类是通过@Component注解方式扫描进Spring的,那么会为每一个class实例化ScannedGenericBeanDefinition来描述bean。

以上就是spring中BeanDefinition以及实现类的相关介绍,在后续讲解spring初始化流程、spring扩展点、第三方如何集成spring的源码中会大量出现BeanDefinition相关的知识。

你可能感兴趣的:(源码,spring,spring,java,后端)