Spring5系列(七) | spring对象的生命周期(全是干货不要错过)

简介: Spring5系列(七) | spring对象的生命周期(全是干货不要错过)

本篇文章,我们再来研究一下一道高频的面试题,就是spring所管理对象的生命周期。


一. 传统类的生命周期


在传统的java应用中,bean的生命周期很简单。使用Java关键字new进行bean的实例化,然后该bean就可以使用了。一旦bean不再被使用,则有java的垃圾回收器自动进行垃圾回收。


二. spring控制的对象的生命周期


相比之下,spring容器中的bean的生命周期就显得相对复杂多了。我们为什么要学习对象的生命周期呢,因为有了spring之后,现在都是由spring来控制对象的创建,存活和销毁,所以学习对象的生命周期,有利于我们更好的了解spring,使用spring.

我们本次对于对象的生命周期主要讲解三个阶段,分别是创建阶段,初始化阶段和销毁阶段。然后在最后,我们在给出一个spring容器中的对象完整的生命周期。


2.1 对象的创建阶段


在这个阶段,我们有必要回忆一个问题,就是spring工厂在什么时候创建的对象。这个我们在初始spring这个章节的注意细节中有提到,不知道大家有没有注意到。但是我们的案例:

<beanid="user"class="com.spring.User"/>
publicstaticvoidmain(String[]args){
//指定spring配置文件并创建工厂ApplicationContextctx=newClassPathXmlApplicationContext("/applicationContext.xml");
// 根据配置文件中的id值获取对象  Useruser= (User)ctx.getBean("user");
}  


然后我们在注意细节中提到,user对象是什么时候创建的呢,其实是在spring工厂创建的时候,也就是当第一行代码,new ClassPathXmlApplicationContext("/applicationContext.xml");这行代码执行完毕的时候,User对象就已经创建出来了。但是要注意的是,并不是说所有的对象都是在工厂创建的同时完成创建,这其实是和对象的scope有关的。


scope我们前面提到,有单例的和非单例的区别。当对象是单例的scope的时候,对象的创建是在工厂创建的同时完成创建的。但是如果对象的scope="prototype" 那么他的创建时机就是在获取对象的时候完成创建。这个大家一定要弄明白。也就是如果此时我再 注解上指定scope="prototype", 那么user对象就在执行getBean的时候才会创建出来。而对于单例的对象,如果我们也想让他在使用的时候再创建可以么,也是可以的,只需要在bean标签中加入lazy-init=true, 代表懒加载,那么这个单例的对象就会在获取的时候才被创建,要注意 lazy-init=true只对单例对象生效。我们可以通过在构造方法中打印一句话,来追踪对象的创建时机。


2.2 对象的初始化阶段

spring工厂在创建对象后,会调用对象的初始化方法,完成对应的初始化操作。 初始化方法的提供: 由程序员根据需要,提供初始化方法,最终完成初始化操作 初始化方法的调用: 由spring工厂完成调用。


也就相当于,如果我们想在spring创建好的对象,执行一些操作,spring为我们提供了一个钩子,我们通过这个钩子书写一段代码,spring工厂可以在创建好对象后帮我们执行这段代码,其实就是一种接口回调的思想,那么接下来我们来看一下,如何实现初始化操作。


方式一:实现InitializingBean接口,重写 void afterPropertiesSet() 方法。

通过方法名称我们就是,这个方法是在属性赋值完成后执行。也就是如果有依赖注入,先进行注入,再执行这个方法。好了我们在验证一下。


publicclassProductimplementsInitializingBean{
publicProduct(){
System.out.println("Product.product");
  }
// 这就是初始化方法,做一些初始化操作,spring会调用@OverridepublicvoidafterPropertiesSet() throwsException{
System.out.println("Product.afterPropertiesSet");
  }
}


然后我们在spring配置文件中,配置Product类,测试了通过工厂获取对象(代码省略)。观察结果


Product.product
Product.afterPropertiesSet

在对象创建之后,执行的该方法。


方式二. 如果有一些类无法实现或不想实现InitializingBean接口,但他们也想做一些初始化的操作怎么办呢,spring也为我们提供了一种方式。


那就是自己定义一个方法,在里边执行初始话的操作,然后指定该方法为初始化方法。

publicclassProduct{
//自定义初始化方法publicvoidmyInit(){
// 初始化操作  }
}
<beanid="product"class="xxx.Product"init-method="myInit"/>


通过在bean标签的init-method属性指定自定义的初始化方法,也能实现初始化的效果。


注意事项:

  1. 如果一个对象既实现了InitializingBean,又提供了普通方法。
    先执行接口的初始化方法,再执行普通的初始化方法。
  2. 如果有属性赋值注入,又有初始化方法,哪个先执行?
    先执行注入操作,再执行初始化方法(afterPropertiesSet) 可通过方法名理解
  3. 什么叫做初始化操作
    主要指对资源的初始化,数据库.....io......网络


2.3 对象的销毁阶段


spring销毁对象前, 会调用对象的销毁方法,完成销毁操作。那么我们就先需要了解几个点。 spring什么时候销毁所创建的对象 工厂关闭的时候,  ctx.close(); //注意该方法只在子类中有,所有不能用多态创建工厂 关于销毁方法: 和初始化方法一样,有程序员完成,由spring调用。如果我们在对象销毁前想执行一些操作,可以下载这个里面


方式一. 实现Disposable接口,重写destroy方法。

publicclassProductimplementsInitializingBean,DisposableBean{
publicProduct(){
System.out.println("Product.product");
  }
// 这就是初始化方法,做一些初始化操作,spring会调用@OverridepublicvoidafterPropertiesSet() throwsException{
System.out.println("Product.afterPropertiesSet");
  }
// 销毁操作,就是资源的释放操作@Overridepublicvoiddestroy() throwsException{
System.out.println("Product.destroy");
  }
}

测试类:

publicclassTest{
// close方法定义在子类中,使用多态无法调用publicstaticvoidmain(String[] args){
ClassPathXmlApplicationContextctx=newClassPathXmlApplicationContext("xxx.xml");
Productp= (Product) ctx.getBean("product");
ctx.close();
  }
}

注意事项:

  1. 销毁方法只有在工厂调用close() 方法的时候才会调用
  2. close方法只有子类中,ApplicationContext中没有,所以不能使用多态调用。


方式二. bean标签指定销毁方法

自己定义销毁方法,配置文件中指定

publicclassProductimplementsInitializingBean,DisposableBean{
publicProduct(){
System.out.println("Product.product");
  }
// 这就是初始化方法,做一些初始化操作,spring会调用@OverridepublicvoidafterPropertiesSet() throwsException{
System.out.println("Product.afterPropertiesSet");
  }
// 销毁操作,就是资源的释放操作@Overridepublicvoiddestroy() throwsException{
System.out.println("Product.destroy");
  }
publicvoidmyDestroy() throwsException(){
System.out.println("Product.myDestroy");
  }
}
<beanid="product"class="xxx.Product"init-method="myInit"destroy-method="myDestroy"/>


细节分析:

  1. 销毁方法的操作只作用于 scope="singleton" 的对象
  2. 销毁操作主要指资源的释放操作
  3. 执行顺序: 接口先于自定义



2.4 后置处理Bean


我们在前面提到了关于类的初始化的方式,除此之外,spring还提供了一个叫做BeanPostProcessor的接口,可以让我们对spring工厂创建的对象进行再加工。他底层的实现原理是通过AOP实现的。我们来看下这个接口。这个接口有两个方法需要实现。

postProcessBeforeInitialization: 该方法执行在初始化代码之前 postProcessAfterInitialization:  该方法执行在初始化代码之后


这里说的初始化方法指的就是我们前面提到的InitializingBean 接口中的afterPropertiesSet方法。

步骤:

  1. 实现BeanPostProcessor
  2. 重写两个方法: postProcessBeforeInitialization             postProcessAfterInitialization
  3. postProcessBeforeInitialization: Spring创建完对象并注入后,可以运行该方法进行加工,通过返回值交给spring框架
  4. postProcessAfterInitialization:spring执行完初始化操作后,进行的加工。
  5. 配置文件配置

这里要特别注意下这两个需要实现的方法,跟前面初始化的方式不太一样,首先这两个方法是默认方法,不实现也不会报错,其次这两个方法有参数,有返回值。我们直接上代码。

  1. 开发一个类
@DatapublicclassCategory{
privateStringname;
privateintage;
}
  1. 交给spring管理,并注入属性
<beanid="c"class="xxx.Category"><propertyname="name"value="张三"/><propertyname="age"value="10"/></bean>复制代码
  1. 创建一个bean的加工工厂,也就是BeanPostProcessor的实现类
publicclassMyBeanPostProcessorimplementsBeanPostProcessor{
@OverridepublicObjectpostProcessBeforeInitialization(Objectbean, StringbeanName) throwsBeansException{
returnbean;
  }
@OverridepublicObjectpostProcessAfterInitialization(Objectbean, StringbeanName) throwsBeansException{
if(beaninstanceofCategory) {
Categoryc= (Category)bean;
c.setName("李四");
    } 
returnbean;
  }  
}
  1. 配置文件配置
<beanid="myBeanPostProcessor"class = "com.xxx.MyBeanPostProcessor"/>

测试,获取c对象,打印name, 发现结果变为李四。

注意事项:

  1. BeanPostProcessor 是对spring工厂中的所有类都生效的,所以使用之前,最好使用 instanceof 做判断,对指定类型进行加工,避免出错。
  2. 由于是对所有类做操作,所以第一个参数 Object bean ,就代表被加工的类,spring工厂的的所有类,都会走这个方法,所以这个bean可能是User,也可能是Product,等等等。第二个参数就是类的名称。
  3. 加工之后,要把对象做返回操作,即使你什么都不处理,也要把bean返回。
  4. 一定要注意执行顺序。


2.5 ApplicationContextAware


这个接口也是spring容器中非常重要的一个接口,关于他的使用场景我们后面会有一个更详细的案例来进行解释。这是先给出一个简单的用法。 spring中提供了很多以Aware结尾的接口。比如BeanNameAware, BeanFactoryAware,EnvironmentAware, 以及我们今天要说的ApplicationContextAware.


Aware翻译过来是知道的,已感知的,意识到的意思。其实就是可以帮我们获取到单词前面的对象。那么我们来说下ApplicationContext, 这个是spring的应用上下文,也可以简单理解成spring的工厂,我们可以通过getBean的方式获取spring工厂所管理的bean ,所以相对而言,它是一个比较重量级的资源,我们不能频繁创建,只需要创建一次就好了。


那么当我们创建好了之后,如果其他地方也想获得这个工厂应该怎么办呢,就可以实现这个ApplicationContextAware接口,在里面就能得到对应的这个工厂,而不需要重复创建。 我们来看用法:


publicclassUserServiceImplimplementsUserServiceimplementsApplicationContextAware{
privateApplicationContextctx;
@OverridepublicvoidsetApplicationContext(AppliactionContextapplication){
this.ctx=application;
  }
@Overridepublicvoidregister(Useruser){
System.out.println("registe----")
// 调用的是原始对象的login方法,---核心功能,切面功能不执行// 设计目的是: 调用代理对象的login方法this.login("abc", "123456");
//获取代理对象UserServiceuserService= (UserService)ctx.getBean("userService");
userService.login("abc", "123456");
  }
@Overridepublicbooleanlogin(Stringname, Stringpassword){
System.out.println("login-----")
  }
}

实现这个接口,需要实现setApplicationContext方法,里边的参数就是当前的工厂,我们把它赋值给自己定义的同类型的成员变量就可以使用了。

同理,BeanNameAware接口,就是可以获得我们在配置文件配置的id值,也是通过重写setBeanName实现。

@DatapublicclassUserimplementsBeanNameAware{
privateStringid;
privateStringname;
privateStringaddress;
publicvoidsetBeanName(StringbeanName) {
//ID保存BeanName的值,就是<bean>标签中的id值id=beanName;
    }
}

三. 总结

好了,本篇文章我们大概完成了,希望大家一定好好理解,真的是干货满满,我也是弄了三天才搞出来。接写来我们就好总结spring bean的生命周期。

  1. 对象的实例化(相当于new了出来)
  2. 填充属性
  3. 调用BeanNameAware的setBeanName方法
  4. 调用BeanFactoryAware的setBeanFacotry方法
  5. 调用ApplicationContextAware的setApplicationContext方法
  6. 调用BeanPostProcessor的postProcessBeforeInitialization方法
  7. 调用InitializingBean的afterPropertySet方法
  8. 调用自定义的初始化方法(init-method = myInit)
  9. 调用BeanPostProcessor的postProcessAfterInitialization方法
  10. bean可以使用了
  11. 容器关闭时调用DisposableBean的destroy方法
  12. 调用自定义的销毁方法

image.png

注意: 上面的步骤主要是为了说明对象生命周期各个阶段的执行顺序,如果实现了对应接口就执行,没实现就不执行。 好了,本篇文章很重要,希望大家有所收获!!!



参考资料:

Spring IN Action(第四版)   中国邮电出版社

孙帅spring详解:www.bilibili.com/video/BV185…



目录
相关文章
|
2月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
2天前
|
存储 Java 程序员
SpringIOC和DI的代码实现,Spring如何存取对象?@Controller、@Service、@Repository、@Component、@Configuration、@Bean DI详解
本文详细讲解了Spring框架中IOC容器如何存储和取出Bean对象,包括五大类注解(@Controller、@Service、@Repository、@Component、@Configuration)和方法注解@Bean的用法,以及DI(依赖注入)的三种注入方式:属性注入、构造方法注入和Setter注入,并分析了它们的优缺点。
6 0
SpringIOC和DI的代码实现,Spring如何存取对象?@Controller、@Service、@Repository、@Component、@Configuration、@Bean DI详解
|
2月前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
3月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
88 11
|
2月前
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
|
2月前
|
前端开发 Java 开发者
|
3月前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
38 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
2月前
|
Java API Spring
Spring5入门到实战------1、Spring5框架概述、入门案例
这篇文章是Spring5框架的入门教程,概述了Spring框架的核心概念和特点,并通过一个创建普通Java类的案例,详细演示了从下载Spring核心Jar包、创建配置文件、编写测试代码到运行测试结果的完整流程,涵盖了Spring IOC容器的使用和依赖注入的基本用法。
|
2月前
|
Java Spring
Spring的Bean生命周期中@PostConstruct注解
【8月更文挑战第3天】在Spring框架中,`@PostConstruct`注解标示Bean初始化完成后立即执行的方法。它在依赖注入完成后调用,适用于资源加载、属性设置等初始化操作。若方法中抛出异常,可能影响Bean初始化。与之对应,`@PreDestroy`注解的方法则在Bean销毁前执行,用于资源释放。
|
5月前
|
存储 Java 数据库
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期