Spring Bean 实例化流程推测:
1. spring 的简单使用
1.1 Spring Xml 方式简单使用
public class SimpleXmlSpringStarter {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application-context.xml");
Person bean = ctx.getBean(Person.class);
System.out.println(bean);
}
}
@Data
class Person{
private String name;
private String gender;
}
使用 new 关键字构造一个 ClassPathXmlApplicationContext,并传入 application-context.xml 后,调用 ClassPathXmlApplicationContext 的 getBean 方法即可得到在 application-context.xml 中配置的 Bean 实例。
所以,在 new ClassPathXmlApplicationContext 过程中,肯定有扫描 Spring Xml 文件,并将文件内的信息转换为某种能够实例化出Spring Bean的机制。
1.2 Spring Annotation 简单使用
public class SimpleAnnotationSpringStarter {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TeacherConfig.class);
Teacher bean = ctx.getBean(Teacher.class);
System.out.println(bean);
}
}
@Configuration
class TeacherConfig{
@Bean
public Teacher teacher(){
Teacher teacher = new Teacher();
teacher.setGender("male");
teacher.setName("jasper");
return teacher;
}
}
@Data
class Teacher{
private String name;
private String gender;
}
在此方案中, 使用关键字 new 了一个 AnnotationConfigApplicationContext 并传入一个Spring 配置类后,即可通过 AnnotationConfigApplicationContext 的 getBean 方法得到一个 Teacher 类的 Bean 实例。
所以可以认为可以判断:Spring 中有一个关键过程:根据传入的 Spring Annotation 配置类解析用户定义的 Bean 信息。
综上:结合 Spring 提供的其他配置 Bean 的方式(properties,yml等)可以推论:Spring 提供一个接口,该接口可以从不同类型的文件(xml,properties, yml 等)中解析用户配置的 Spring 配置信息以供 Spring 后期统一管理(实例化),为此,我们可以抽象出一个接口:BeanReader。
2. Spring高度灵活的可配置性
在实际使用 Spring 的过程中,我们有可能需要在 Spring 实例化Bean之前,对Bean 的属性做一些修改,比如:我们需要将 单例的 Bean 修改为 会话内有效(scope由singleton 改为 session(在一次会话中保持单例,多个会话之间不为单例)) ,将原本配置为非懒加载的Bean 配置为懒加载。
所以可推论:Spring 在实例化 Bean 之前,解析配置文件(xml,annotation,yml等)之后,存在一种机制可以保存配置文件中配置的 Bean的定义信息(我们可定义一个接口BeanDefinition来专门保存Spring配置文件中定义的信息,而在1.中抽象的接口 BeanReader 可以改名为 BeanDefinitionReader),这些定义信息不仅包含了用户定义的Bean的属性,还包含了与Spring相关的属性,如:初始化方法(标签<init-method>),是否单例等。
Spring 为用户提供一种方式可改变配置文件中定义的Bean的与Spring 相关的属性,我们可将这种方式封装为一个接口:BeanDefinitionPostProcessor, 而 整个 Spring Application Context 为一个Bean工厂,BeanDefinition 为Bean工厂(BeanFactory)的次级原材料(初级原材料为各种配置文件信息)。BeanDefinitionPostProcessor 修改与Bean工厂相关的属性,故此:可将接口:BeanDefinitionPostProcessor 改名为:BeanFactoryPostProcessor,其负责修改Bean工厂相关的一些属性。
3. Spring Bean的实例化
Java世界中,实例化的方式归根结底,可通过 new 关键字来调用类的构造器实例化,也可通过反射获取类的Class对象,通过调用newInstance方法来实例化,或者是通过Class 获取构造器,再通过构造器调用newInstance方法来实例化。而 new 关键字的方式实例化Bean 需要 new 之后指定类的构造方法,如果这种方式来实例化Spring Bean,那么将丧失灵活性,所以可推论,在Spring 中,实例化Bean的方式一定是通过反射来实现。
@Bean
public Teacher teacher(){
Teacher teacher = new Teacher();
teacher.setGender("male");
teacher.setName("jasper");
return teacher;
}
而对于那些通过 @Bean 注解来实例化 Bean 对象的方法,我们可以大胆推测其将 @Bean注解标注的方法封装为一个 生产 Spring Bean的Provider 函数式接口对象(ObjectFacotry<T>),并将该方法的信息封装为一个BeanDefinition, 并将ObjectFactory 对象封装到BeanDefinition中,当Spring实例化此@Bean 对象时,最终通过调用封装的ObjectFactory 接口,来构造出我们需要的Bean对象。
4. Spring Bean 对 Spring容器本身访问的支持
在生产过程中,我们可能在自定义的Bean中需要获取Spring容器相关的一些信息,所以需要提供一些方法,让Spring调用这些方法,并将我们需要的Spring容器相关的信息注入到我们自定义的Bean中,如,我们需要在Bean中获取Spring 上下文:
@Getter
@Configuration
class TeacherConfig implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public Teacher teacher(){
Teacher teacher = new Teacher();
teacher.setGender("male");
teacher.setName("jasper");
return teacher;
}
}
我们还可以获取如:BeanName,BeanFactory,Environment 等信息,均可通过实现相应的Aware接口来获取。
所以可推测: Spring 实例化Bean时可能会搜集那些实现了相关Aware接口的实例,并在相应的时间调用相应的Aware方法。
5. 解决循环依赖:
@Data
@Component
class Teacher{
@Autowired
private Student student;
private String name;
private String gender;
}
@Data
@Component
class Student{
@Autowired
private Teacher teacher;
private String name;
private String gender;
}
上诉代码中,Teacher 依赖Student, Student以来 Teacher,这种情况称之为循环依赖。在Spring实例化的推测中,我们得到Spring在非@Bean注解配置的Bean实例化是通过反射来实例化,所以在上面被循环依赖的Teacher和Student 在两个Bean实例化时均应是默认值(null)。而Spring向我们提供的Bean 被依赖的属性都是已经赋值了的。
所以,我们可推测:Spring Bean实例化与初始化(赋值依赖属性)过程是分开的,且Spring有一种机制,可解决循环依赖问题。
循环依赖解决方案假设: Spring先实例化 Teacher,在初始化Teacher以后,发现它依赖一个属性类型为Student,于是Spring内部调用getBean方法,获取Student实例,发现没有,于是转而实例化Student,而实例化Student时发现其依赖Teacher,而转而又去实例化Teacher,此时如果不做提供解决机制,最终的结果是:不停的调用getBean,且发现对方依赖的均是未实例化的对象,最终造成栈溢出异常,启动失败。为解决这个问题,我们可以将实例化后,但尚未初始化的Bean放入一个缓存空间,同样是上面的流程,Teacher实例化后,将Teacher未初始化实例放入缓存,再查找其依赖的Student,发现没有,则实例化Student,然后初始化Student,发现其依赖Teacher,此时先从缓存中获取Teacher,并引用,此时Student Bean生产完毕,然后将Student 注入Teacher。而Student因为引用该Teacher,而Teacher Bean生产完毕,这样也就解决了循环依赖的问题。
而,这也是为什么Spring要将实例化与初始化分开的原因。
特别的,如果使用全参构造实例化和注入依赖,将无法解决循环依赖的问题,因为其实例化时先查找依赖的实例,而被依赖的实例实例化时也先查找其本身。最终都查找不到,造成循环依赖失败。
6. Spring的扩展性(遵循开闭原则,对扩展开放,对修改关闭)
我们在使用Spring过程中,会碰到一个注解@Transactional ,这个注解为我们提供了事务操作。标记了此注解的方法可开启事务。如果方法内不抛出异常,则事务执行并提交,如果发生异常,则回滚。但是我们在方法中并没在 try catch 中写关于回滚的方法。所以,可推测:Spring-tx模块提供了回滚功能,且有可能Spring-tx将回滚功能增强到我们的代码中,但时考虑到其他模块也有类似的场景,如:Spring AOP,所以可确定,这种增强其他功能的操作应由Spring 提供。而它可以像BeanFactoryPostProcessor改变BeanDefinition一样,可增强实例化后的Bean,所以我们可命名为BeanFactoryPostProcessor,功能为:扩展Bean的功能,但是不能改变Bean的功能。
对于扩展功能,我们可在其原有功能前扩展,也可在功能后扩展,所以推测其至少有两种方案。而Spring Bean 在完全实例化后Spring 本身将不再对 Bean 进行操作(Bean销毁以外),所以,可推测其扩展的应该是Bean初始化方法(标签<init-method>指定的方法,与解决循环依赖的初始化不同)
综上:我们可以推测 Spring 实例化Bean 的流程为:
—>>> 加载配置文件(xml,annotation,yaml...) ,定义接口 BeanDefinitionReader
—>>> 将配置文件定义的信息转换为 BeanDefinition ,定义接口 BeanDefinition
—>>> 扩展BeanDefinition 信息 , 定义接口: BeanFactoryPostProcessor
—>>> 实例化Bean , 实例化方案(反射,构造器, ObjectFactory)
—>>> 初始化(依赖属性赋值),并解决循环依赖, 引入缓存
—>>> 扩展Bean功能 ,定义接口: BeanPostProcessor,推测通过此接口实现AOP
—>>> 扩展初始化之前的功能 ,定义方法:BeanPostProcessor#beforeBeanInitialize
[ spring bean 初始化,调用<init-method>指定的方法]
—>>> 扩展初始化之后的功能 , 定义方法:BeanPostProcessor#afterBeanInitialize
后面,我们将对以上推论(标色为 棕黄色 部分)进行检验。