首页 > 编程语言 >Spring源码分析(九)Spring中Bean的生命周期(上)

Spring源码分析(九)Spring中Bean的生命周期(上)

时间:2023-09-08 14:02:04浏览次数:38  
标签:Spring stop bean 接口 start Bean 源码 方法 public

在这篇文章之前,写过了官网上容器扩展点相关的知识,包括 FactoryBean,BeanFactoryPostProcessor,BeanPostProcessor,其中 BeanPostProcessor 还剩一个很重要的知识没有介绍,就是相关的 BeanPostProcessor 中方法的执行时机。之所以在之前的文章没有写这块内容,主要是涉及到 bean 的生命周期。这篇文章开始写 Bean 的生命周期相关的知识,整个 bean 的生命周期可以分为以下几个阶段:

  • 实例化(得到一个还没有经过属性注入和初始化的对象)
  • 属性注入(得到了一个经过属性注入还没有初始化的对象)
  • 初始化(得到一个初始化还没有经过 AOP 的对象,AOP 会在后置处理器中执行)
  • 销毁

上面几个阶段中,BeanPostProcessor 将会穿插执行。而在初始化和销毁又分为两部分

  • 生命周期回调方法的执行
  • aware 相关接口方法的执行

这篇文章中,我们先完成 bean 的生命周期整个初始化的学习,对比官网中的章节为 1.6 小节

生命周期回调

  1. Bean 初始化回调

实现初始化回调有三种形式:

  • 实现 InitializingBean 接口

如下:

public class AnotherExampleBean implements InitializingBean {
   @Override
   public void afterPropertiesSet() throws Exception {
   }
}
  • 使用 Bean 标签中的 init-method 属性

配置如下:

<bean id="exampleInitBean" class="com.xxl.root.ExampleBean" init-method="init"/>
public class ExampleBean {
   private void init() {
   }
}
  • 使用@PostConstruct 注解

配置如下:

public class ExampleBean {
   @PostConstruct
   private void init() {
   }
}
  1. Bean 销毁回调

实现销毁回调方法有三种形式:

  • 实现 DisposableBean 接口
public class DisExampleBean implements DisposableBean {
   @Override
   public void destroy() throws Exception {
   }
}
  • 使用 Bean 标签中的 destroy-method 属性
<bean id="exampleInitBean" class="com.xxl.root.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
   private void cleanup() {
   }
}
  • 使用@PreDestroy 注解
public class ExampleBean {
   @PreDestroy
   private void cleanup() {
   }
}
  1. 配置默认的初始化及销毁方法

我们可以通过如下这种配置,为多个 bean 同时指定初始化或销毁方法

<beans default-init-method="init" default-destroy-method="destory">
   <bean id="blogService" class="com.something.DefaultBlogService">
      <property name="blogDao" ref="blogDao" />
   </bean>
</beans>

在上面的 XML 配置中,Spring 会将所有处于 beans 标签下的 bean 的初始化方法名默认为 init,销毁方法名默认为 destory。

  1. 执行顺序

如果我们在配置中同时让一个 bean 实现了回调接口,又在 bean 标签中指定了初始化方法,还进行了@PostContruct 注解配置的话,那么它们的执行顺序如下:

  1. 被@PostConstruct 所标记的方法
  2. InitializingBean 接口中的 afterPropertiesSet()方法
  3. Bean 标签中的 init()方法

对于销毁方法执行顺序如下:

  1. 被@PreDestroy 所标记的方法
  2. DisposableBean 接口中的 destroy()方法
  3. Bean 标签中的 destroy()方法

我们可以总结如下:

注解的优先级 > 实现接口的优先级 > XML 配置的优先级

同时还需要注意,官网推荐我们使用注解的形式定义生命周期回调犯法,这是因为相比于实现接口,采用注解这种形式使我们代码和 Spring 框架本身的耦合度更加低。

  1. 容器启动或停止回调 Lifecycle 接
  • Lifecycle 接口

Spring源码分析(九)Spring中Bean的生命周期(上)_初始化

编写 Demo 如下:

public class Test {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
      ac.start();
      ac.stop();
   }
}


@Configuration
@ComponentScan("com.xxl.service")
public class Appconfig {


}


@Component
public class LifeCycleService implements Lifecycle {
   boolean isRunning;
   @Override
   public void start() {
      isRunning = true;
      System.out.println("LifeCycleService start");
   }


   @Override
   public void stop() {
      isRunning = false;
      System.out.println("LifeCycleService stop");
   }


   @Override
   public boolean isRunning() {
      return isRunning;
  }

运行上面的代码可以发现程序正常打印启动和停止的日志,在上面的例子中需要注意的是,一定要在 start 方法执行时将容器的运行状态 isRunning 置为 true,否则 stop 方法不会调用

在 Spring 容器中,当接收到 start 和 stop 信号时,容器会将这些传递到所有实现了 Lifecycle 的组件上,在 Spring 内部是通过 LifecycleProcessor 接口来完成这一功能的。

  • LifecycleProcessor

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_02

  • 从上面的代码我们可以知道 LifecycleProcessor 本身也是 Lifecycle 接口的扩展,它添加了两个额外的方法在容器刷新和关闭时执行。
  1. 当我们实现 Lifecycle 接口时,如果我们想要其 start 或 stop 执行,必须显示的调用容器的 start()或 stop()方法。
  2. stop 方法不一定能保证在我们之前介绍的销毁方法之前执行。

当我们在容器中对多个 bean 配置了容器启动会停止时的调用,那么这些 bean 中的 start 方法和 stop 方法调用的顺序就很重要了。如果两个 bean 之间有明确的依赖关系,比如我们通过@DependsOn 注解,或者@AutoWired 注解向容器表明了 bean 之间的依赖关系,如下:

@Component
@DependsOn("b")
public class A {
// @Autowired
// B b;
}


@Component
public class B {
}

这种情况下,b 作为被依赖项,其 start 方法会在 a 的 start 方法前调用,stop 方法会在 a 的 stop 方法后调用。

但是,在某些情况下 bean 之间没有直接的依赖关系,可能我们只知道实现了接口 A 的所有 bean 的方法优先级要高于实现了接口 B 的 bean。在这种情况下,我们就需要用到 SmartLifecycle 接口

  • SmartLifecycle

其继承关系如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_03

它本身除了继承 ifecycle 接口,还继承了一个 Phased 接口,其接口定义如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_04

通过上面接口定义的方法,我们可以指定不同 bean 方法回调方法执行的优先级。

再来看 SmartLifecycle 接口本身的定义:

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_05

一般情况下,我们并不会重写 isAutoStartup 和 stop 方法,但是为了指定方法执行的优先级,我们通常会覆盖其中的 getPhase 方法,默认情况下它的优先级最低。我们需要知道的是,当我们启动容器时,如果有 bean 实现了 SmartLifecycle 接口,其 getPhase()方法返回的值越小,那么对于 start 方法执行的时间就越早,stop 方法执行的时机就越晚。因此,一个实现了 SmartLifecycle 的对象,它的 getPhase()方法返回 Integer.MIX_VALUE 将是第一个执行 start 方法的 bean 和最后一个执行 stop 方法的 bean。

另外我们可以看到

源码分析,分为两个阶段:

  • 启动阶段,整个流程图如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_06

我们主要分析的代码在其中的 3-12-2 和 3-12-3 步骤中

3-12-2 解析,代码如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_07

这段代码很简单,就做了一件事:判断当前容器中是否有一个 lifecycleProcessor 的 bean 或者 BeanDefinition。如果有的话,采用这个提供的 lifecycleProcessor,如果没有的话直接 new 一个 DefaultLifecycleProcessor。这个类主要负责将启动或停止信息传播到具体的 bean 中,我们稍后分析的代码基本都在这个类。

3-12-3 解析:

其中 getLifecycleProcessor(),就是获取我们上一步提供的 lifecycleProcessor,然后调用 onRefresh 方法,代码如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_初始化_08

之后调用 startBeans 方法:

跟踪代码可以发现,start 方法最终调用到了 doStart 方法:

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_09

上面的逻辑可以归结为第一句话:获取这个 bean 依赖的其他 bean,在启动时先启动其依赖的 bean,这也验证了我们从官网得出的结论。

  • 停止阶段

停止容器有两种方法,一种是显示的调用容器的 stop 或者 close 方法,如下:

public class Test {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
      ac.register(Appconfig.class);
      ac.refresh();
      ac.stop();
      //      ac.close();
   }
}

而另外一种就是主场一个 JVM 退出时的钩子,如下:

public class Test {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
      ac.register(Appconfig.class);
      // 当 main 函数运行完成后,会调用容器 doClose 方法
      ac.registerShutdownHook();
      ac.refresh();
   }
}

无论是上面哪一种方法,最终都会调用到 DefaultLifecycleProcessor 的 onClose 方法,代码如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_10

Spring源码分析(九)Spring中Bean的生命周期(上)_初始化_11

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_12

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_13

整个 stop 方法和 start 方法对比,逻辑上并没有很大的区别,除了执行时顺序相反外:

  • start 方法,先找出这个份 bean 的所有依赖,然后启动这个 bean 的依赖
  • stop 方法,先找出哪些 bean 依赖了当前 bean,然后停止这些被依赖的 bean,之后再停止当前的 bean。

Aware 接口

在整个 bean 的生命周期的初始化阶段,有一个很重要的步骤就是执行相关的 Aware 接口,而整个 Aware 接口执行又可以分为两个阶段:

  • 第一个阶段,执行 BeanXXXAware 接口
  • 执行其它 Aware 接口

至于为什么需要这样分,我们在进行源码分析的时候就明白了

我们可以发现,所有的 Aware 接口都是为了能让我们拿到容器中相关的资源,比如 BeanNameAwar,可以拿到 bean 的名称,ApplicationContextAware 可以拿到整个容器。但是使用 Aware 接口也会相应的带来一些麻烦,当我们去实现这些接口时,意味着我们的应用程序和 Spring 容器发生了强耦合,违背了 IOC 的原则。所以一般情况下,不推荐这种方式,除非在编写一些整个应用基础的组件。

Spring 内部提供了如下这些 Aware 接口:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_14

初始化过程源码分析

回顾我们之前的流程图,可以看到,创建 bean 的动作主要发生在 3-11-6-4 步骤中,主要分为三步:

  • createBeanInstance,创建实例
  • populateBean,属性注入
  • initializeBean,初始化

我们今天要分析的代码主要就是 3-11-6-4-3 步,其完成的功能主要就是初始化,相对于我们之前分析的代码来说,这段代码算比较简单:

Spring源码分析(九)Spring中Bean的生命周期(上)_初始化_15

第一步:执行部分 aware 接口中的方法

Spring源码分析(九)Spring中Bean的生命周期(上)_生命周期_16

可以看到,在 invokeAwareMethods 这个方法中,并不是所有的 Aware 接口都会被执行,只有 BeanNameAware,BeanClassLoaderAware,BeanFactoryAware 这三个接口会被执行,这也是为什么我单独讲 BeanXXXAware 这一类的接口划为一组的原因。这三个 Aware 接口分别实现的功能:

BeanNameAware:获取 bean 的名字

BeanClassLoaderAware:获取加载这个 bean 的类加载器

BeanFactoryAware:获取当前的 BeanFactory

第二步:完成 Aware 接口方法的执行,以及@PostConstructor,@PreDestroy 注解的处理

  • Aware 接口执行,除了上面介绍的三个 Aware 接口,其余的接口都会在这个阶段执行,例如我们之前说的 ApplicationContestAware 接口,它会被一个专门的后置处理器 ApplicationContextAwareProcessor 处理,其余的接口也是类似的操作,
  • @PostConstructor,@PreDestroy 两个注解的处理,这两个注解会被 CommonAnnotationBeanPostProcessor 这个后置处理器处理,需要注意的是@Resource 注解也是被这个后置处理器处理的。

第三步:执行 InitializingBean 完成初始化方法执行

Spring源码分析(九)Spring中Bean的生命周期(上)_初始化_17

整段代码的逻辑还是很简单的,先判断是否实现了对象的生命周期回调的接口(InitializingBean),如果实现了接口,先调用接口中的 afterPropertiesSet 方法,之后再判断是否提供了 initMethod,也就是 XML 中的 Bean 标签中的 init-method 属性。

第四部:完成 AOP 代理

AOP 代理实现的具体过程以后在写,暂且只需要知道 AOP 是在 bean 完成了所有初始化方法后完成即可。也不难理解,在进行 AOP 之前必须保证我们的 bean 已经被充分装配了。

总结

就目前而言,我们可以将整个 Bean 的生命周期总结如下:

Spring源码分析(九)Spring中Bean的生命周期(上)_优先级_18

在上图中,实例化和属性注入的过程还没有分析,在后续的文章中在详细些了。销毁节点并不复杂,这里也不做分析了,直接给出结论,大概可以直接阅读代码,入口在容器的 close 方法。

另外,我这里并没有实现 LifeCycle 接口的 bean 中的 start 方法和 stop 方法算入到整个 bean 的生命周期中,大家只有知道,如果实现了 SmartLifecyle 接口,那么在容器启动时也会默认调用其 start 方法,并且调用的时机在 bean 完成初始化后,而 stop 方法将在 bean 销毁前调用。

标签:Spring,stop,bean,接口,start,Bean,源码,方法,public
From: https://blog.51cto.com/u_15668812/7409529

相关文章

  • 手术麻醉临床信息系统源码,覆盖从患者入院,经过术前、术中、术后,至出院的全过程管理
    手术麻醉临床信息系统源码手术麻醉临床信息系统功能符合三级甲等医院评审要求,实现与医院现有信息系统如HIS、LIS、PACS、EMR等系统全面对接,全面覆盖从患者入院,经过术前、术中、术后,直至出院的全过程。通过与相关医疗仪器的设备集成,可以轻松集成手术室传统监护设备如监护仪、麻醉机......
  • 自定义配置文件参数在application可以直接识别Not registered via @EnableConfigurati
    自定义配置文件参数在application可以直接识别Notregisteredvia@EnableConfigurationPropertiesormarkedasSpringcomponent看见很多开源项目的配置文件可以直接配置在application.yaml中,自己也想弄一个,怎么弄呢?这是我的demo,你正常ConfigurationProperties会报错Notregi......
  • 为什么 springboot 项目中 使用 lombok 不需要指定版本
    springboot默认管理了lombok的版本依赖,所以不需要指定版本号SpringBoot项目中使用Lombok不需要显式指定Lombok的版本,是因为SpringBoot的父项目(spring-boot-starter-parent)已经为您管理了Lombok的版本。这是通过在SpringBoot的父项目中的dependencyManagement部分指定Lombok的......
  • SpringSecurity中注解讲解
    目录[email protected]@PreAuthorize1.1.1开启注解1.1.2使用注解原生方法1.1.3使用注解自定义方法[email protected]@Secured2其他注解[email protected]@PreFilter3权限表达式1@EnableGlobalMethodSecurity@EnableGlobalMethodSecurity是Spring......
  • springmvc中的json数据转为字符串使用到的jar包,将servlet设置为bean对象
    2023-09-08<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version></dependency>ServletConfigp......
  • 直播APP源码搭建:核心的服务器系统
     在现代科技的推动下,网络衍生出了各种各样的技术,每个技术都被应用到需要的APP上,直播APP源码搭建出来的APP就是其中的一个,然而,这些技术能够成功的在直播APP源码搭建的APP中稳定的为用户们提供功能与服务,还要依托一个关键的系统,它是直播APP源码搭建出的平台核心,这个系统就是服务器......
  • springboot实现 伪微信登录
    众所周知,微信扫码登陆的功能,个人网站是无法申请的,我们想在本地测一下微信登录也是无法实现。要实现微信登录,首先你得是一个企业单位,有公章才能申请,申请还要花费300块大洋。如果我们只是想学习和体验一下微信登录,可以自己本地搭建个微型服务模拟一下,过一把瘾也是可以的。如果你是企......
  • 详谈SpringBoot启动项目后执行自定义方法的方式
    在main启动函数中调用这个是在所有启动后执行,也是常用之一。@SpringBootApplicationpublicclassListenerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ListenerApplication.class,args);System.out.println("启动成......
  • Linux系统上安装.tar.gz格式的Python源码包
    要在Linux系统上安装.tar.gz格式的Python包,您可以按照以下步骤进行操作:解压文件:使用以下命令将.tar.gz文件解压缩:tar-zxvfpackage.tar.gz这将在当前目录下创建一个包含源代码的新文件夹。进入源代码目录:使用cd命令进入解压后的源代码目录:cdpackage检查依赖库:执行以下命令检查......
  • springboot - idea - active: @profileActive@ 有时候 不识别 @ 导致启动失败
    1.背景有时候正常,有时候不行,特别是maven执行了clean命令后 2.解决右键执行一下这个即可 ......