bean的生命周期

2023-11-06

六、bean的生命周期

https://liayun.blog.csdn.net/article/details/110670961
在这里插入图片描述

Bean 的初始化和销毁;

1、@Bean注解中使用init-method属性和destroy-method属性来指定初始化方法和销毁方法。
在这里插入图片描述

初始化和销毁方法的时机:

你有没有想过这样一个问题,初始化方法和销毁方法是在什么时候被调用的啊?

  • bean对象的初始化方法调用的时机:对象创建完成,如果对象中存在一些属性,并且这些属性也都赋好值之后,那么就会调用bean的初始化方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会自动调用bean的初始化方法;对于多实例bean来说,在每次获取bean对象的时候,调用bean的初始化方法。
  • bean对象的销毁方法调用的时机:对于单实例bean来说,在容器关闭的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器不会管理这个bean,也就不会自动调用这个bean的销毁方法了。不过,小伙伴们可以手动调用多实例bean的销毁方法。

InitializingBean

在这里插入图片描述
在这里插入图片描述

多实例bean的生命周期不归Spring容器来管理,这里的DisposableBean接口中的方法是由Spring容器来调用的,所以如果一个多实例bean实现了DisposableBean接口是没有啥意义的,因为相应的方法根本不会被调用,当然了,在XML配置文件中指定了destroy方法,也是没有任何意义的。所以,在多实例bean情况下,Spring是不会自动调用bean的销毁方法的。

Spring中提供了一个InitializingBean接口,该接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

//@Component
public class Cat implements InitializingBean, DisposableBean {

    public Cat(){
        System.out.println("Cat...constructor...构造器");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Cat...destroy....");
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        System.out.println("Cat...afterPropertiesSet...");
    }
}

InitializingBean源码:

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {

   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
      if (logger.isTraceEnabled()) {
         logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
      }
      if (System.getSecurityManager() != null) {
         try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
               ((InitializingBean) bean).afterPropertiesSet();
               return null;
            }, getAccessControlContext());
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         ((InitializingBean) bean).afterPropertiesSet();
      }
   }

   if (mbd != null && bean.getClass() != NullBean.class) {
      String initMethodName = mbd.getInitMethodName();
      if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
         invokeCustomInitMethod(beanName, bean, mbd);
      }
   }
}

分析上述代码后,我们可以初步得出如下信息:

1、Spring为bean提供了两种初始化的方式,实现InitializingBean接口(也就是要实现该接口中的afterPropertiesSet方法),或者在配置文件或@Bean注解中通过init-method来指定,两种方式可以同时使用。

2、实现InitializingBean接口是直接调用afterPropertiesSet()方法,与通过反射调用init-method指定的方法相比,效率相对来说要高点。但是init-method方式消除了对Spring的依赖。

3、如果调用afterPropertiesSet方法时出错,那么就不会调用init-method指定的方法了。

小结

也就是说Spring为bean提供了两种初始化的方式,第一种方式是实现InitializingBean接口(也就是要实现该接口中的afterPropertiesSet方法),第二种方式是在配置文件或@Bean注解中通过init-method来指定,这两种方式可以同时使用,同时使用先调用afterPropertiesSet方法,后执行init-method指定的方法。


 *Spring容器创建完成时,会自动调用单实例bean的构造方法,对单实例bean进行了实例化操作。
 *  * 多实例的bean在容器关闭的时候是不进行销毁的,也就是说你每次获取时,IOC容器帮你创建出对象交还给你,
 * 至于要什么时候销毁这是你自己的事,Spring容器压根就不会再管理这些多实例的bean了。
 *          * 单实例:在容器启动的时候创建对象;
            * 多实例:在每次获取的时候创建对象;
 *  * 初始化:
 *      对象创建好,并赋值好,调用初始化方法;
 *  * 销毁:
 *      单实例:容器关闭的时候;
 *      多实例:容器不会管理Bean,只是负责创建,容器不会调用销毁方法;
 *  * 1、指定初始化和销毁的方法;
 * 2、通过Bean 实现InitializingBeanDisposableBean
 *      InitializingBean(定义初始化逻辑)
 *      DisposableBean(定义销毁逻辑)

2、@PostConstruct和@PreDestroy这俩注解

  • @PostConstruct:在bean创建完成并且属性赋值完成之后,来执行初始化方法
  • @PreDestroy:在容器销毁bean之前通知我们进行清理工作
//@Component
public class Dog {


    private Cat cat;

    public Dog(){
        System.out.println("dog...constructor..构造器");
    }
    
    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }
    
    //对象创建赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("dog...@PostConstruct...");
    }

    //容器移除对象之前
    @PreDestroy
    public void destory(){
        System.out.println("dog...@PreDestroy...");
    }
}

3、通过让bean实现BeanPostProcessor接口

@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization--->"+beanName+"--->"+bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization--->"+beanName+"--->"+bean);
        return bean;
    }

    @Override
    public int getOrder() {
        return 3;
    }
}

通过以上这四种方式,我们就可以对bean的整个生命周期进行控制:

bean的实例化:调用bean的构造方法,我们可以在bean的无参构造方法中执行相应的逻辑。
bean的初始化:在初始化时,可以通过BeanPostProcessor的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法进行拦截,执行自定义的逻辑;通过@PostConstruct注解、InitializingBean和init-method来指定bean初始化前后执行的方法,在该方法中咱们可以执行自定义的逻辑。
bean的销毁:可以通过@PreDestroy注解、DisposableBean和destroy-method来指定bean在销毁前执行的方法,在该方法中咱们可以执行自定义的逻辑。

所以,通过上述四种方式,我们可以控制Spring中bean的整个生命周期。
在这里插入图片描述

BeanPostProcessor后置处理器作用

后置处理器可用于bean对象初始化前后进行逻辑增强
Spring提供了BeanPostProcessor接口的很多实现类,例如AutowiredAnnotationBeanPostProcessor用于@Autowired注解的实现,AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的动态代理等等。

除此之外,我们还可以自定义BeanPostProcessor接口的实现类,在其中写入咱们需要的逻辑。下面我会以AnnotationAwareAspectJAutoProxyCreator为例,简单说明一下后置处理器是怎样工作的。

我们都知道spring AOP的实现原理是动态代理,最终放入容器的是代理类的对象,而不是bean本身的对象,那么Spring是什么时候做到这一步的呢?就是在AnnotationAwareAspectJAutoProxyCreator后置处理器的postProcessAfterInitialization方法中,即bean对象初始化完成之后,后置处理器会判断该bean是否注册了切面,若是,则生成代理对象注入到容器中。这一部分的关键代码是在哪儿呢?我们定位到AbstractAutoProxyCreator抽象类中的postProcessAfterInitialization方法处便能看到了,如下所示。

在这里插入图片描述
这里请看:https://liayun.blog.csdn.net/article/details/110442093 这里是源码讲解。我这里直接拿过来结果吧。

在applyBeanPostProcessorsBeforeInitialization()方法中,会遍历所有BeanPostProcessor对象,然后依次执行所有BeanPostProcessor对象的postProcessBeforeInitialization()方法,一旦BeanPostProcessor对象的postProcessBeforeInitialization()方法返回null以后,则后面的BeanPostProcessor对象便不再执行了,而是直接退出for循环。这些都是我们看源码看到的。

看Spring源码,我们还看到了一个细节,在Spring中调用initializeBean()方法之前,还调用了populateBean()方法来为bean的属性赋值, 这在上面我也已经说过了。

这里数属性填充工作:那么是怎样工作的那?
就是调用populateBean()方法

经过上面的一系列的跟踪源码分析,我们可以将关键代码的调用过程使用如下伪代码表述出来。

populateBean(beanName, mbd, instanceWrapper); // 给bean进行属性赋值
initializeBean(beanName, exposedObject, mbd)
{
	applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	invokeInitMethods(beanName, wrappedBean, mbd); // 执行自定义初始化
	applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

也就是说,在Spring中,调用initializeBean()方法之前,调用了populateBean()方法为bean的属性赋值,为bean的属性赋好值之后,再调用initializeBean()方法进行初始化。

在initializeBean()中,调用自定义的初始化方法(即invokeInitMethods())之前,调用了applyBeanPostProcessorsBeforeInitialization()方法,而在调用自定义的初始化方法之后,又调用了applyBeanPostProcessorsAfterInitialization()方法。至此,整个bean的初始化过程就这样结束了。

概述

如果我们现在自定义的组件中需要用到Spring底层的一些组件,比如ApplicationContext(IOC容器)、底层的BeanFactory等等,那么该怎么办呢?先说说自定义的组件中能不能用Spring底层的一些组件吧?既然都这样说了,那么肯定是能够的。

回到主题,自定义的组件要想使用Spring容器底层的一些组件,比如ApplicationContext(IOC容器)、底层的BeanFactory等等,那么只需要让自定义组件实现XxxAware接口即可。此时,Spring在创建对象的时候,会调用XxxAware接口中定义的方法注入相关的组件。

XxxAware接口概览

其实,我们之前使用过XxxAware接口,例如,我们之前创建的Dog类,就实现了ApplicationContextAware接口,Dog类的源码如下所示。

package com.meimeixia.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * ApplicationContextAwareProcessor这个类的作用是可以帮我们在组件里面注入IOC容器,
 * 怎么注入呢?我们想要IOC容器的话,比如我们这个Dog组件,只需要实现ApplicationContextAware接口就行
 * 
 * @author liayun
 *
 */
@Component
public class Dog implements ApplicationContextAware {
	
	private ApplicationContext applicationContext;

	public Dog() {
		System.out.println("dog constructor...");
	}
	
	// 在对象创建完成并且属性赋值完成之后调用
	@PostConstruct
	public void init() { // 在这儿打个断点调试一下
		System.out.println("dog...@PostConstruct...");
	}
	
	// 在容器销毁(移除)对象之前调用
	@PreDestroy
	public void destory() {
		System.out.println("dog...@PreDestroy...");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 在这儿打个断点调试一下
		// TODO Auto-generated method stub
		this.applicationContext = applicationContext;
	}
	
}

从以上Dog类的源码中可以看出,实现ApplicationContextAware接口的话,需要实现setApplicationContext()方法。在IOC容器启动并创建Dog对象时,Spring会调用setApplicationContext()方法,并且会将ApplicationContext对象传入到setApplicationContext()方法中,我们只需要在Dog类中定义一个ApplicationContext类型的成员变量来接收setApplicationContext()方法中的参数,那么便可以在Dog类的其他方法中使用ApplicationContext对象了。

其实,在Spring中,类似于ApplicationContextAware接口的设计有很多,本质上,Spring中形如XxxAware这样的接口都继承了Aware接口,我们来看下Aware接口的源码,如下所示。
在这里插入图片描述
接下来,我们看看都有哪些接口继承了Aware接口,如下所示。
在这里插入图片描述

XxxAware接口案例

接下来,我们就挑选几个常用的XxxAware接口来简单的说明一下。

ApplicationContextAware接口使用的比较多,我们先来说说这个接口,通过ApplicationContextAware接口我们可以获取到IOC容器。

首先,我们创建一个Red类,它得实现ApplicationContextAware接口,并在实现的setApplicationContext()方法中将ApplicationContext输出,如下所示。

package com.meimeixia.bean;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
 * @author liayun
 *
 */
public class Red implements ApplicationContextAware {
	
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("传入的IOC:" + applicationContext);
		this.applicationContext = applicationContext;
	}

}

其实,我们也可以让Red类同时实现几个XxxAware接口,例如,使Red类再实现一个BeanNameAware接口,我们可以通过BeanNameAware接口获取到当前bean在Spring容器中的名称,如下所示。

package com.meimeixia.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
 * @author liayun
 *
 */
public class Red implements ApplicationContextAware, BeanNameAware {
	
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("传入的IOC:" + applicationContext);
		this.applicationContext = applicationContext;
	}

	/**
	 * 参数name:IOC容器创建当前对象时,为这个对象起的名字
	 */
	@Override
	public void setBeanName(String name) {
		System.out.println("当前bean的名字:" + name);
	}

}

当然了,我们可以再让Red类实现一个EmbeddedValueResolverAware接口,我们通过EmbeddedValueResolverAware接口能够获取到String值解析器,如下所示。

package com.meimeixia.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.StringValueResolver;

/**
 * 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
 * @author liayun
 *
 */
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
	
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("传入的IOC:" + applicationContext);
		this.applicationContext = applicationContext;
	}

	/**
	 * 参数name:IOC容器创建当前对象时,为这个对象起的名字
	 */
	@Override
	public void setBeanName(String name) {
		System.out.println("当前bean的名字:" + name);
	}
	
	/**
	 * 参数resolver:IOC容器启动时会自动地将这个String值的解析器传递过来给我们
	 */
	@Override
	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我的年龄是#{20*18}");
		System.out.println("解析的字符串:" + resolveStringValue);
	}

}

IOC容器启动时会自动地将String值的解析器(即StringValueResolver)传递过来给我们用,咱们可以用它来解析一些字符串,解析哪些字符串呢?比如包含#{}这样的字符串。我们可以看一下StringValueResolver类的源码,如下所示。
在这里插入图片描述

接着,我们需要在Red类上标注@Component注解将该类添加到IOC容器中,如下所示。

@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

最后,运行IOCTest_Autowired类中的test02()方法,输出的结果信息如下所示。
在这里插入图片描述

XxxAware原理

XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类。 例如,我们这里以ApplicationContextAware接口为例,ApplicationContextAware接口的底层原理就是由ApplicationContextAwareProcessor类实现的。从ApplicationContextAwareProcessor类的源码可以看出,其实现了BeanPostProcessor接口,本质上是一个后置处理器。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

bean的生命周期 的相关文章

随机推荐

  • 【Spring AOP】面向切面编程的概念,实践,原理详解

    AOP概念 AOP的全名是aspect oriented programming面向切面编程 它是面对对象编程 OOP 的一种方式 这个AOP的思想主要是指对一个事务的集中处理 将多个类都要完成的功能都在一个类中统一完成 如用户登录的校验功
  • Linux下软件安装:Openblas安装

    一 apt安装 sudo apt get install libopenblas dev 二 手动从source安装 1 下载OpenBLAS并编译 1 git clone https github com xianyi OpenBLAS
  • 代码管理平台—GitHub

    1 GitHub介绍 GitHub是一个开源的代码托管平台 使用GitHub可以查看别人的项目 可以建立静态网页 可以管理插件 可以在线编译 可以托管代码等等 1 1 GitHub使用方法 注册用户 网址 https github com
  • C++内存四大区域

    文章目录 前言 所划分的内存区块有 代码区解析 全局区解析 1 全局变量的地址 2 静态变量的地址 3 常量 字符常量及const全局常量 栈区解析 1 普通局部变量 2 const修饰局部变量 3 栈区注意事项 堆区解析 new的用法 1
  • 在 React 中运行 Vue react-vue

    react vue 详细介绍 React Vue旨在连接 React 和 Vue 帮助您在 React 中运行 Vue 用途 使用Vue 的 Reactivity 系统来观察React组件 使用 react vue loader 以在Rea
  • pycharm打开多个项目并存

    问题 有时我们需要打开多个项目 而现在的做法是 原有的a项目不动 新打开一个pycharm来打开b项目 或者 在原有的a项目中打开b项目并覆盖a项目 即a项目与b项目不能共存 需求 有时我们只想打开一个pycharm 但想打开多个项目 方法
  • LSTM多输入多输出解决数据回归预测问题matlab

    文章目录 引言 LSTM 简介 LSTM多输入多输出matlab实现 结论 引言 在许多数据分析和预测问题中 我们需要使用机器学习算法来处理多输入多输出的数据回归预测问题 长短期记忆网络 Long Short Term Memory 简称
  • 国内期货怎么交易?

    国内期货怎么交易 期货市场 是按达成的协议交易并按预定日期交割的金融市场 现货与期货的显著区别是 期货的交割期放在未来 而价格 交货及付款的数量 方式 地点和其他条件是在即期由买卖双方在合同中规定的 商品及证券均可在期货市场上交易 虽然合同
  • Flutter 的键值存储数据库

    Flutter 键值存储数据库 键值存储是开发中十分常见的需求 在Flutter开发中 一般使用 shared preferences 插件来实现 shared preferences 本质上就是将键值对保存到一个XML文件中进行持久化 而
  • Spring Boot请求403 Forbidden错误

    欢迎关注笔者的微信公众号 在项目中引入了Spring Security框架做权限控制 但是出于调试的方便 在开始的时候就禁用了Spring Security 计划到项目后期再专门开发 但是 在调试的过程中发现 Postman发出的请求后台无
  • 语音识别芯片LD3320介绍再续

    语音识别芯片LD3320驱动程序 1 芯片复位 复位就是对LD3320芯片的第47腿 RSTB 发送低电平 然后需要对片选CS做一次拉低 拉 高的操作 以激活内部DSP 按照以下顺序 void LD reset RSTB 1 delay 1
  • Python脚本,实现验证码识别

    别验证码是一个很复杂的任务 需要使用计算机视觉和机器学习算法 这里我只能提供一个大致的思路和代码示例 还需要进行相应的调整和补充 import tensorflow as tf from PIL import Image import nu
  • airtest上的滑动操作swipe

    正常来说 方法一的滑动是生效的 但是在页面有蒙层或是其他怪异的情况下 可能就不生效了 再用方法二 方法一 获取设备的高度和宽度 width height device get current resolution 校准滑动的起点和终点 因为
  • 电容滤波笔记

    电容滤波效果 公式 容抗 F 1000Hz 干扰频率 C 1000uF X C 1 2
  • linux 修改jdk版本 /usr/src/jdk-11.0.11/bin/java: 无法执行二进制文件

    下载的jdk版本与linux匹配不正确 更换成上图中就可以了
  • 关于尚硅谷禹神Vue视频四十二级v-cloak,delay_server服务器服务器的替代方案

    关于四十二级v cloak的替代方案 资料没找到那个delay server服务器 手写了一个替代的代码 有的话发我一下谢谢 吃瓜 原视频链接 http 尚硅谷Vue2 0 Vue3 0全套教程丨vuejs从入门到精通 https www
  • flex 间距_聊一聊Flex布局

    Flex布局目前对于前端来说已经是一个非常熟悉且常用的布局方案了 有了它 我们很大程度上就可以避免原来让人头秃的正常流布局带来的很多IFC inline formatting context 问题 随着浏览器的支持性不断增强 基本上我们日常
  • 双系统下怎么卸载linux系统,双系统怎么卸载其中一个操作系统 双系统卸载其中一个操作系统方法...

    一 WIN7 XP 卸载XP 1 登录win7 放入xp光盘 或者用虚拟光驱加载镜像 2 点击 开始 运行 输入 C boot bootsect exe nt52 all force 这里假设C盘为xp安装盘符 回车运行 3 重启电脑 发现
  • 招聘30000名应届生,研发占80%,秋招之光还得是比亚迪!

    金秋9月 一年一度的 秋招 在互联网大厂中如期打响 在过去的一年 互联网圈子经历了几番 动荡 今年 各大公司在人才储备 岗位设置等方面显现出哪些新动向 整个行业的趋势如何 本篇文章我们来一探究竟 01 超3万名应届生入职 比亚迪成为 秋招之
  • bean的生命周期

    六 bean的生命周期 https liayun blog csdn net article details 110670961 Bean 的初始化和销毁 1 Bean注解中使用init method属性和destroy method属性来