当前位置: 首页 > news >正文

Spring注解驱动之InitializingBean和DisposableBean

概述

在上一讲中,我们讲述了如何使用@Bean注解来指定bean初始化和销毁方法,具体的用法就是在@Bean注解中使用init-method属性和destroy-method属性来指定初始化和销毁方法。除此之外,Spring还提供了相应的接口来对bean进行初始化、销毁。

InitializingBean接口

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

public interface InitializingBean {

	void afterPropertiesSet() throws Exception;
}

根据InitializingBean接口中提供的afterPropertiesSet()方法的名字不难推断出,afterPropertiesSet()方法是在属性赋值之后调用的。
那到底是不是这样的呢?下面我们来分析下afterPropertiesSet()方法的调用时机。

何时调用InitializingBean接口

我们定位到Spring的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory这个类里面的invokeInitMethods()方法中,来查看Spring加载bean的方法。

	protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {
		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
						@Override
						public Object run() throws Exception {
							((InitializingBean) bean).afterPropertiesSet();
							return null;
						}
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				((InitializingBean) bean).afterPropertiesSet();
			}
		}
		if (mbd != null) {
			String initMethodName = mbd.getInitMethodName();
			if (initMethodName != null && !(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指定的方法。

DisposableBean接口

DisposableBean接口概述

实现org.springframework.beans.factory.DisposableBean接口的bean在销毁前,Spring将会调用DisposableBean接口的destroy()方法。也就是说我们可以实现DisposableBean这个接口定义销毁的逻辑。

public interface DisposableBean {
	void destroy() throws Exception;
}

可以看到,在DisposableBean接口中只定义了一个destroy方法。
在bean生命周期接收前调用destroy方法做一些收尾工作,亦可以使用destroy-method。前者与Spring耦合度高,使用类型强转.方法名(),效率高;后者耦合低,使用反射,效率相对来说较低。

DisposableBean接口注意事项

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

单实例bean案例

首先,创建一个Cat的类来实现InitializingBean和DisposableBean这俩接口,代码如下所示,注意该Cat类上标注了一个@Component注解。

package com.meimeixia.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class Cat implements InitializingBean, DisposableBean {
	
	public Cat() {
		System.out.println("cat constructor...");
	}

	/**
	 * 会在容器关闭的时候进行调用
	 */
	@Override
	public void destroy() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("cat destroy...");
	}

	/**
	 * 会在bean创建完成,并且属性都赋好值以后进行调用
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("cat afterPropertiesSet...");
	}
}

然后,在MainConfigOfLifeCycle配置类中通过包扫描的方式将以上类注入到Spring容器中。

package com.meimeixia.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.meimeixia.bean.Car;

@ComponentScan("com.meimeixia.bean")
@Configuration
public class MainConfigOfLifeCycle {

	@Scope("prototype")
	@Bean(initMethod="init", destroyMethod="destroy")
	public Car car() {
		return new Car();
	}	
}

接着,运行IOCTest_LifeCycle类中的test01()方法,输出的结果信息如下所示。
在这里插入图片描述
从输出的结果信息中可以看出,单实例bean情况下,IOC容器创建完成后,会自动调用bean的初始化方法;而在容器销毁前,会自动调用bean的销毁方法。

多实例bean案例

多实例bean的案例代码基本与单实例bean的案例代码相同,只不过是在Cat类上添加了一个@Scope(“prototype”)注解,如下所示。

package com.meimeixia.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component
public class Cat implements InitializingBean, DisposableBean {
	
	public Cat() {
		System.out.println("cat constructor...");
	}

	/**
	 * 会在容器关闭的时候进行调用
	 */
	@Override
	public void destroy() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("cat destroy...");
	}

	/**
	 * 会在bean创建完成,并且属性都赋好值以后进行调用
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("cat afterPropertiesSet...");
	}
}

然后,我们在IOCTest_LifeCycle类中新增一个test02()方法来进行测试,如下所示。

@Test
public void test02() {
    // 1. 创建IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成");
    System.out.println("--------");
    
    // 调用时创建对象
    Object bean = applicationContext.getBean("cat");
    System.out.println("--------");
    
    // 调用时创建对象
    Object bean1 = applicationContext.getBean("cat");
    System.out.println("--------"); 
    
    // 关闭容器
    applicationContext.close();
}

接着,运行IOCTest_LifeCycle类中的test02()方法,输出的结果信息如下所示。
在这里插入图片描述
从输出的结果信息中可以看出,在多实例bean情况下,Spring不会自动调用bean的销毁方法。

参考

Spring注解驱动开发第13讲——使用InitializingBean和DisposableBean来管理bean的生命周期,你真的了解吗?

相关文章:

  • 微信输入法来了,如何下载?
  • Metacat实现原理解析
  • MTK Camera Senor Bring up 复盘总结
  • map有关的运算符重载
  • Java项目硅谷课堂学习笔记-P9-整合网关与实现订单和营销管理模块
  • 我的周刊(第055期)
  • 【GraphSAGE实践】YelpChi评论图数据集上的反欺诈检测
  • 基于单目和低成本GPS的车道定位方法
  • MyBatisPlus——多表查询——多条件查询——分页查询
  • 面向跨模态匹配的噪声关联学习
  • Java中的反射
  • java毕业设计旅游网站设计mybatis+源码+调试部署+系统+数据库+lw
  • Java基础进阶-序列化
  • 02.3 线性代数
  • java毕业设计木材产销系统的生产管理模块mybatis+源码+调试部署+系统+数据库+lw
  • @angular/forms 源码解析之双向绑定
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • Docker入门(二) - Dockerfile
  • flask接收请求并推入栈
  • jquery cookie
  • mysql 5.6 原生Online DDL解析
  • Python十分钟制作属于你自己的个性logo
  • React中的“虫洞”——Context
  • sessionStorage和localStorage
  • vue-router 实现分析
  • 分享几个不错的工具
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 微信小程序填坑清单
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • #Linux(权限管理)
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (2)Java 简介
  • (2022 CVPR) Unbiased Teacher v2
  • (C语言)二分查找 超详细
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (超详细)语音信号处理之特征提取
  • (二)Eureka服务搭建,服务注册,服务发现
  • (二)pulsar安装在独立的docker中,python测试
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • ***测试-HTTP方法
  • .NET 读取 JSON格式的数据
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .NET下ASPX编程的几个小问题
  • .net项目IIS、VS 附加进程调试
  • .net专家(高海东的专栏)
  • .skip() 和 .only() 的使用
  • @Controller和@RestController的区别?
  • @FeignClient 调用另一个服务的test环境,实际上却调用了另一个环境testone的接口,这其中牵扯到k8s容器外容器内的问题,注册到eureka上的是容器外的旧版本...
  • [ C++ ] STL_vector -- 迭代器失效问题