1. 生命周期管理概述
Spring生命周期,完整的名称是Spring Bean生命周期管理,也就是Spring中Bean的创建、使用以及销毁的过程。
关于Spring Bean的生命周期,需要达成2项学习结果:
- 能够完整介绍Spring Bean的生命周期
- 能够通过生命周期管理方法,参与到Bean的生命周期中
为了更好的理解Spring Bean的生命周期,可以先了解生命周期管理方法的概念,再逐步掌握Spring Bean生命周期各阶段的细节。
2. 生命周期管理方法
2.1 生命周期管理方法概述
生命周期管理方法是对象在创建、初始化和销毁时自动调用的一些方法。这些方法提供了对象在不同生命周期阶段的行为。
生命周期管理方法的设计在第三方组件或框架中非常常见,主要是为了保证框架的可扩展性。
生命周期管理方法在一些场景中也被称为“生命周期回调方法”,“生命周期钩子函数”等。
理解生命周期管理方法的切入点在于“自动调用”,也就是说不是由开发者手动调用。
我们先来看一下由开发者手动调用的生命周期管理方法的示例。如下图所示,开发者在编码的过程中决定了何时创建对象、何时使用对象、何时销毁对象,这些方法的调用时机由开发者决定。
但是当使用第三方组件和框架时,组件或框架中的对象的生命周期一般不是由开发者控制,而是按照特定的流程执行。
例如Java EE中经典的HttpServlet组件,其生命周期完全由Web服务器控制。此时,如果开发者想要参与到这些组件的生命周期中,在特定的节点执行某段自定义的逻辑,就需要依靠生命周期管理方法。
简单来说,组件的设计者约定了在特定的节点会调用特定的方法,开发者可以通过重写该方法,达到在特定节点执行自定义逻辑的目的。
2.2 @PostConstruct
Spring 为了方便用户扩展功能,提供了Bean的生命周期管理方法。为了简化方法的声明,Spring提供了通过注解声明生命周期管理方法的功能。
@PostConstruct注解用于将Spring Bean中的方法声明为生命周期管理方法,该方法将在Bean实例化后被调用。
- 被标记的方法要求无参且返回值类型为void
- 不能标记静态方法
- 对应的方法一般用于初始化:如初始化缓存,初始化数据库连接等
- 在依赖注入之后被调用
- 对于Scope为Singleton的Bean,默认在容器创建完成后进行实例化和初始化
- 对于Scope为Prototype的Bean,在第一次获取该实例时进行实例化和初始化
新建一个Maven项目,在项目中引入Spring框架的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
新建com.obj.spring.TagService类,并声明初始化方法:
package com.obj.spring;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
// @Scope("prototype")
public class TagService {
/**
* 本地缓存,缓存了标签信息,线程安全集合,扩展下知识广度
*/
private final CopyOnWriteArrayList<String> tags = new CopyOnWriteArrayList<>();
@PostConstruct // 创建对象以后调用
public void initTags(){
tags.add("应季");
tags.add("爆款");
tags.add("进口");
tags.add("生鲜");
tags.add("健身");
System.out.println("初始化标签:"+tags.toString());
}
public List<String> getTags() {
return tags;
}
}
新建com.obj.spring.ContextConfig类,声明Spring框架配置:
package com.obj.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.obj.spring")
public class ContextConfig {
}
新建com.obj.spring.Test类,编写测试内容:
package com.obj.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
// 测试@PostConstruct执行时机:创建容器 - > 实例化Bean -> 初始化
System.out.println("第一次获取Bean");
TagService service = context.getBean(TagService.class);
service.getTags().forEach(System.out::println);
}
}
在com.obj.spring.TagService的类前添加@Scope("prototype")注解,重新运行案例,观察@PostConstruct标记的方法执行时机的变化。
2.3 @PreDestroy
@PreDestroy注解用于将Spring Bean中的方法声明为生命周期管理方法,该方法将在Bean实例被销毁前执行。
- 被标记的方法要求无参且返回值类型为void
- 不能标记静态方法
- 对应的方法一般用于清空缓存,释放资源等
- 关闭容器时候会自动调用
- 只有Scope为Singleton的Bean,销毁时候才会执行销毁方法
- 这个方法不是绝对可靠,依赖JVM的关闭流程,直接终止进程时不会执行
在com.obj.spring.TagService中添加销毁方法:
package com.obj.spring;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
public class TagService {
private final CopyOnWriteArrayList<String> tags = new CopyOnWriteArrayList<>();
@PostConstruct // 创建对象以后调用
public void initTags(){
tags.add("应季");
tags.add("爆款");
tags.add("进口");
tags.add("生鲜");
tags.add("健身");
System.out.println("初始化标签:"+tags.toString());
}
public List<String> getTags() {
return tags;
}
@PreDestroy
public void destroy(){
System.out.println("清除标签缓存:"+tags.toString());
tags.clear();
}
}
在com.obj.spring.Test中添加关闭Spring Context的操作:
package com.obj.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
// 测试@PostConstruct执行时机:创建容器 - > 实例化Bean -> 初始化
System.out.println("第一次获取Bean");
TagService service = context.getBean(TagService.class);
service.getTags().forEach(System.out::println);
// 关闭容器 -> 销毁Bean -> 调用@PreDestroy标记的方法
((AnnotationConfigApplicationContext)context).close();
}
}
在com.obj.spring.TagService的类前添加@Scope("prototype")注解,重新运行Test类,查看销毁方法是否还会被调用。
2.4 @PreDestroy原理
@PreDestroy的执行原理:
1、Spring在Java虚拟机上挂了关闭钩子,虚拟机关闭时候会自动执行钩子, Spring的钩子会关闭Spring容器。
2、关闭容器的过程中,会销毁所有单例的Bean
3、销毁单例的Bean时,会调用添加了@PreDestroy 注解的方法。
如下图所示:
这一过程,也就是Bean生命周期中的销毁阶段。如下图所示:
2.5 JVM Hook示例
package com.obj.spring;
import java.util.concurrent.TimeUnit;
public class ShutdownHookDemo {
public static void main(String[] args) throws Exception{
/*
* runtime 代表正在运行的虚拟机
* 可以通过runtime获取当前虚拟机的参数
*/
Runtime runtime = Runtime.getRuntime();
//获取当前JVM总内存数量
long bytes = runtime.totalMemory();
System.out.println("totalMemory:" + bytes/1024/1024+"MB");
//给系统挂关闭钩子
runtime.addShutdownHook( new Thread( () -> System.out.println("执行钩子了!") ) );
System.out.println("挂完了");
TimeUnit.SECONDS.sleep(2);
}
}
2.6 initMethod和destroyMethod
当程序中使用的Bean是第三方提供的类时,无法直接在源码中添加@PostConstruct或@PreDestroy注解,此时可以通过@Bean注解的属性实现。
Spring为 @Bean注解提供了initMethod属性和destroyMethod属性,用于设置Bean的初始化方法和销毁方法,语法如下:
// init和destroy为Bean中两个方法的名称
@Bean(initMethod="init", destroyMethod="destroy")
声明com.obj.spring.ThirdPartyClass,模拟一个第三方类:
package com.obj.spring;
import jakarta.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ThirdPartyClass {
// 本地缓存,缓存了名字信息
private CopyOnWriteArrayList<String> names = new CopyOnWriteArrayList<>();
@PostConstruct
public void init(){
names.add("Tom");
names.add("Jerry");
names.add("Andy");
System.out.println("初始化ThirdPartyClass缓存:"+names);
}
public List<String> getNames() {
return names;
}
public void destroy(){
names.clear();
System.out.println("销毁ThirdPartyClass缓存:"+names);
}
}
声明Test2类,编写测试逻辑:
package com.obj.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test2 {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
ThirdPartyClass thirdParty = context.getBean(ThirdPartyClass.class);
// 关闭容器 -> 销毁Bean -> 调用@PreDestroy标记的方法
((AnnotationConfigApplicationContext)context).close();
}
}
3. 认识Spring容器
3.1 Spring容器概述
Spring容器是一种IoC容器,它负责创建、配置和管理Bean。Spring容器的基本特点和作用:
1、容器负责实例化和管理bean,通过使用Spring容器,可以将对象的创建和配置解耦合。
2、容器提供了一种可插拔的机制,使得应用程序能够更加灵活地配置和管理bean。
3、容器支持IoC和AOP,通过使用IoC容器,开发者可以更加方便地实现依赖注入和AOP编程。
BeanFactory和ApplicationContext是Spring容器的两个主要实现。
3.2 BeanFactory
BeanFactory接口是Spring容器的基本接口,提供了Bean的管理方法。BeanFactory既是生产Bean的工厂,同时也是管理Bean的仓库。
在BeanFactory中,所有的bean都是懒加载的,只有在使用时才会创建,因此它的启动速度比其他容器要快。
Spring在BeanFactory的基础上扩展了ApplicationContext。根据Spring官方文档,除了极少数情况下,开发者应该使用ApplicationContext而非BeanFactory。
3.3 BeanFactory和FactoryBean的关系
BeanFactory 和 FactoryBean 都是 Spring 框架中用于管理和创建对象的重要接口,但它们的作用和用途有所不同:
1、BeanFactory是Bean的工厂,而FactoryBean是工厂类型的Bean。
2、BeanFactory是IOC容器的核心接口,职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
3、FactoryBean 是一个特殊的工厂接口,用于创建和管理特定类型的对象,允许定制化对象创建逻辑,并将工厂本身作为 Bean 进行管理。
4、在实际使用中,BeanFactory 是 Spring 的核心容器接口,而 FactoryBean 则是一种用于创建定制化对象的机制。
3.4 ApplicationContext
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。
在前面的示例中我们实际使用的Spring容器是AnnotationConfigApplicationContext对象,它间接实现了ApplicationContext接口,简化的继承和实现关系如下图所示:
4. 总结
1、生命周期管理方法是对象在创建、初始化和销毁时自动调用的一些方法
- 这些方法提供了对象在不同生命周期阶段的行为
- 生命周期管理方法的设计在第三方组件或框架中非常常见,主要是为了保证框架的可扩展性
2、@PostConstruct注解用于将Spring Bean中的方法声明为生命周期管理方法,该方法将在Bean实例化后被调用
- 被标记的方法要求无参且返回值类型为void
- 不能标记静态方法
- 对应的方法一般用于初始化:如初始化缓存,初始化数据库连接等
- 在依赖注入之后被调用
3、@PreDestroy注解用于将Spring Bean中的方法声明为生命周期管理方法,该方法将在Bean实例被销毁前执行
- 被标记的方法要求无参且返回值类型为void
- 不能标记静态方法
- 对应的方法一般用于清空缓存,释放资源等
- 关闭容器时候会自动调用
- 只有Scope为Singleton的Bean,销毁时候才会执行销毁方法
4、@PreDestroy的执行原理
- Spring在Java虚拟机上挂了关闭钩子,虚拟机关闭时候会自动执行钩子, Spring的钩子会关闭Spring容器
- 关闭容器的过程中,会销毁所有单例的Bean
- 销毁单例的Bean时,会调用添加了@PreDestroy 注解的方法
5、Spring为 @Bean注解提供了initMethod属性和destroyMethod属性,用于设置Bean的初始化方法和销毁方法。
6、Spring容器是一种IoC容器,它负责创建、配置和管理Bean,BeanFactory和ApplicationContext是Spring容器的两个主要实现。
- BeanFactory接口是Spring容器的基本接口,提供了Bean的管理方法。BeanFactory既是生产Bean的工厂,同时也是管理Bean的仓库。
- ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。