前言

Spring的核心功能有三点IOC、DI、AOP,IOC则是基础,也是Spring功能的最核心的点之一。

IoC(控制反转)

IoC是Spring框架的核心原则之一,它是一种设计模式,也称为依赖反转。在传统的开发中,对象通常负责管理其依赖关系,而在IoC中,控制权反转,由容器负责管理对象的生命周期和依赖关系。

核心概念:

  • 容器(Container): Spring的IoC容器是一个负责创建、管理和组装应用程序组件的容器。最常见的容器是BeanFactory和ApplicationContext。

  • Bean(组件): Bean是Spring管理的应用程序对象。这些对象通过容器的配置来创建和管理,通常被称为IoC容器中的组件。

  • 配置元数据(Configuration Metadata): 配置元数据是指告诉容器如何创建和组装Bean的信息,通常使用XML配置、注解或Java配置方式提供。

  • 依赖注入(DI): DI是IoC的一种具体实现,它指的是容器将依赖关系注入到Bean中,而不是Bean自己负责管理它们的依赖。这可以通过构造函数注入、属性注入或方法注入来实现。

DI(依赖注入)

DI是IoC的一种具体实现,它是指容器负责将组件的依赖关系注入到组件中,而不是由组件自己创建或查找依赖。

DI可以通过以下三种方式实现:

  • 构造函数注入
  • 属性注入
  • 方法注入(Setter方法注入)

AOP(面向切面编程)

AOP是一种编程范式,它允许我们将应用程序的不同关注点分离开来,例如日志记录、事务管理、安全性等。

核心概念:

在Spring中,AOP通过代理机制实现,主要使用以下几个关键概念:

  • 切面(Aspect): 切面是一种模块化的方式来处理横切关注点(cross-cutting concerns),例如日志记录或性能监控。

  • 连接点(Join Point): 连接点是在应用程序执行过程中可以被拦截的点,例如方法调用或异常抛出。

  • 通知(Advice): 通知是在连接点上执行的操作,包括前置通知(在方法执行之前执行)、后置通知(在方法执行之后执行)、异常通知(在方法抛出异常时执行)和环绕通知(在方法执行前后都执行)等。

  • 切点(Pointcut): 切点是一组连接点的集合,它定义了在何处应用通知。

Spring的AOP功能允许我们通过配置将通知与切点关联起来,从而实现横切关注点的模块化管理。

 

通过切面和切入点,我们可以将横切关注点(如日志记录)从应用程序的核心业务逻辑中分离出来,从而实现了模块化和松耦合的代码。这使得我们可以更轻松地维护和扩展应用程序。

 

Spring中的一个Bean从生到灭要经历很多过程,总体分为Bean定义、实例化、属性赋值(依赖注入)、初始化、生存期、销毁几个阶段: image.png

下面是一个细化的Bean生命周期图: image.png 过程比较复杂,重点关注Bean的定义、初始化、销毁过程,可以抓住重点: BeanPostProcessor接口可以对Bean生命周期中的很多部分进行扩展,并且Spring容器中有很多内建的BeanPostProcessor对Spring Bean的功能进行支持。BeanPostProcessor不仅在IOC上发挥了重要作用,在AOP上也发挥了重要的作用。搞懂了Spring内置的BeanPostProcessor的功能,基本上就把Spring Bean的生命周期搞懂了。

一、Spring创建bean的流程图

下图是笔者多次翻看IOC源码后总结出来的bean 创建的详细过程,借助该图可以很快的理解相关源码 image.png

二、Spring创建bean的详细流程

上面的流程图其实已经可以很清晰的看到bean的创建过程了,这里结合图片我们一起来详细说下这个过程,这里不贴源码,贴了源码只会让观看的人比较迷糊,若是想跟源码的可以对照上面的流程图完全能做到源码复现,bean创建的这个过程大致可以分为五步:加载bean信息,实例化bean,bean属性填充,初始化bean,后置操作,那我们就基于这五大步来看看Spring是如何创建bean的。

1.加载bean信息

被IOC注解修饰的类,或者通过xml配置的类,首先在容器启动时一refresh方法为入口,会将这些类扫描进来形成BeanDefinition信息,BeanDefinition就是包含了我们配置的一些bean的属性,比如是否单例,是否有bean依赖(DependOn),bean的名称,bean所属class的全路径等,这里存储的相当于bean的元信息,然后通过 BeanDefinitionRegistry将这些BeanDefinition加载进来后面我们就可以利用该信息了,且在Spring创建Bean的全程都需要BeanDefinition的参与,所以他很重要。

2.实例化bean

通过上面的图可以清晰看到在实例化阶段之前其实还有很多小的操作:容器会先去尝试getBean–>doGetBean–>getSingleton等操作在这些操作都拿不到对象以后才会开始着手创建对象,需要说的是getSingleton会尝试从三级缓存中依次去获取Bean,当所有缓存都获取不到时就可以确认当前bean没有被创建,然后就可以启动创建的相关动作。

  • 利用BeanDefinition检查是否有依赖的bean(配置了@DependOn注解)如有,需要先加载依赖bean。
  • 利用BeanDefinition检查是否单例bean,是走单例bean的创建流程,不是再判断是否是原型bean,是走原型bean创建,否则都是另一套路径创建。
  • 开始实例化,调用getSingleton,此时传入的是对象工厂(ObjectFactory)的实现类,因为对象工厂是函数式接口,这里传入的其实就是createBean‘的lamda表达式。
  • 将当前bean加入到正在创建bean的一个set。
  • 调用对象工厂的getObject方法,因为我们再上面已经传入了对象工厂(通过lamda表达式传入)这里相当于调用刚刚的lamda表达式,调用里面的createBean方法。
  • createBean去调了doCreateBean又调了createBeanInstance,在这里底层通过反射技术获取构造参数将对象创建了出来,此时的对象只是通过空参构造创建出来的对象,他并没有任何的属性。
  • 调用addSingletonFactory将实例化完成的bean加入到三级缓存,到这里实例化就算是结束了。

3.bean属性填充

属性填充其实就为自身属性进行赋值的过程,根据我们的DI注解这里会先从三个缓存中获取bean,若是获取不到,则会尝试进行bean的创建,若是走到了bean的创建,则会重新走一边bean创建的整个流程,这里是递归逻辑。

  • populateBean 该方法是填充属性的入口,传入beanName和BeanDefinition。
  • 从BeanDefinition中获取属性注入相关信息然后判断是名称注入还是类型注入。
  • 调用getSingleton从容器中获取所需对象,若是获取不到则会重走对象创建的整个流程,拿到完整对象后将其给到当前bean的属性,到这里属性填充就结束了。

4.初始化bean

属性填充完毕后并没有立即结束这个过程,还有一些其他的操作需要spring进行处理,比如aware接口的处理,postprocessor接口的处理,初始化的处理等操作其实这里主要就是处理这三个动作的。

  • 判断有无实现aware接口,如有则去执行他的实现类的实现方法,所有aware接口可以参考上图中所列的三个aware接口,在spring初始化时会对他们进行是否实现的判断。
  • 获取容器中所有postprocessor接口,然后开始执行他的前置方法。
  • 判断有无实现初始化接口InitializingBean如有则去执行初始化方法afterPropertiesSet。
  • 执行postprocessor的后置方法,通过前置和后置方法我们可以实现自定义的一些逻辑,不过需要注意的是这些前置和后置方法会作用到所有bean。

5.后置操作

这里的后置操作,主要是完成一些清扫工作和适配工作,比如删除二级、三级缓存中无用的bean引用等,下面是具体操作。

  • 将bean从创建中的集合中删除。
  • 将bean加入到单例池中将其从二级三级缓存中删除。
  • 对对象进行一些适配操作,到这里完成了初始化的所有操作,后面就是一步步返回调用的地方了。

看了这五步,不知道是不是对bean的创建过程有了清晰的认识,如果还是不够清晰可以根据第一部分的流程图走下代码,代码走两遍其实就会比较清晰了。

三、bean的生命周期

bean的生命周期其实就是从创建到销毁,上面创建已经说完了,其实只差销毁这一步了。bean销毁发生在容器关闭时对单例bean进行清除操作。在Spring中我们通常有三种方式定义bean销毁时的逻辑

  • 1.通过PreDestroy注解修饰方法 Bean销毁时会检查有无该注解修饰的方法,如有,会对该注解修饰的方法进行执行
  • 2.通过指定destroy-method方法 在使用xml对bean进行注入时,我们可以指定init-method方法,也可以指定destroy-method方法,同样的使用Bean注解时也是支持这两个属性的,Spring容器关闭时会寻找当前bean有无指定destroy-method,如有则会进行执行
  • 3.实现DisposableBean接口 实现该接口重写他的destroy方法,同样的Spring容器关闭时也会检查有无实现该接口,如有实现也会执行这里的销毁方法

下面是对于三种销毁方式的测试代码

第一段是自定义启动Spring容器,给容器注册钩子,这样当我们关闭Spring容器时会自动调用我们的销毁方法

public class AppStartClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(yuCloseSpring.class);
        annotationConfigApplicationContext.start();
        annotationConfigApplicationContext.registerShutdownHook();
    }
}

这一段是测试代码了,分别使用三种方式写了销毁方法

public class MyDisposableBean implements DisposableBean{

    @Override
    public void destroy() throws Exception {
        System.out.println("执行DisposableBean的销毁方法");

    }

    public void test(){
        System.out.println("执行destroy-method销毁方法");
    }

    @PreDestroy
    public void testPreDestroy(){
        System.out.println("执行PreDestroy注解修饰的销毁方法");
    }

}

@Configuration
class yuCloseSpring{

    @Bean(destroyMethod = "test")
    public MyDisposableBean getMyDisposableBean(){
        return  new MyDisposableBean();
    }
}

下面是启动main方法后的执行截图,可以清晰的看到三种销毁方法都是正常执行的,且他们执行顺序是固定的,即:PreDestroy–>DisposableBean–>destroy-method。 image.png 到这里其实bean整个生命周期就算是彻底结束了。

四、总结

这篇主要总结Spring中bean的创建过程,主要分为 加载bean信息–>实例化bean–>属性填充–>初始化阶段–>后置处理等步骤,且每个步骤Spring做的事情都很多,这块源码还是很值得我们都去看一看的。而Spring中Bean的声明周期其实就是创建到使用到销毁,使用应该没啥需要说的,销毁在第三部分也正常介绍了三种销毁的方式。

 

参考: https://blog.csdn.net/m0_62468521/article/details/132642089

https://www.cnblogs.com/imok520/p/16411309.html

https://blog.csdn.net/m0_46897923/article/details/129850717

https://blog.csdn.net/weixin_53287520/article/details/139327336

https://blog.51cto.com/u_14815984/2532327

https://blog.csdn.net/qq_36756682/article/details/124386473