🏡浩泽学编程:个人主页
🔥 推荐专栏:《深入浅出SpringBoot》《java项目分享》
《RabbitMQ》《Spring》《SpringMVC》
🛸学无止境,不骄不躁,知行合一
文章目录
- 前言
- 一、生命周期
- 二、作用域
- 总结
前言
前面我们讲诉了将Bean正确地装配到IoC容器,却未讲诉IoC如何装配和销毁Bean。本篇文章主要讲诉一下Bean的生命周期和作用域。
一、生命周期
Bean 的生命周期的过程, 它大致分为Bean定义、Bean 的初始化、 Bean 的生存期和 Bean 的销毁4个部分。 其中 Bean 定义过程大致如下:
- Spring 通过我们的配置,如@ComponentScan 定义的扫描路径去找到带有@Component 的类,
这个过程就是一个资源定位的过程。- 一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始
化Bean,也就没有Bean 的实例,它有的仅仅是Bean 的定义。- 然后就会把Bean 定义发布到 Spring IoC 容器中。 此时, IoC 容器也只有Bean 的定义,还是
没有Bean 的实例生成。完成了这3 步只是一个资源定位并将Bean 的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。在默认的情况下, Spring会继续去完成Bean 的实例化和依赖注入,这样从IoC 容器中就可以得到一个依赖注入完成的Bean。 但是,有些Bean会受到变化因素的影响,这时我们倒希望是取出 Bean 的时候完成初始化和依赖注入,换句话说就是让那些 Bean 只是将定义发布到IoC 容器而不做实例化和依赖注入, 当我们取出来的时候才做初始化和依赖注入等操作。
Spring Bean的初始化过程:
ComponentScan 中还有一个配置项 lazyI nit,只可以配置 Boolean 值,且默认值为 false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值。
引入例子:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的.。
代码如下:
//定义人类接口
public interface Person {
void service();
void setAnimal(Animal animal);
}
//定义动物接口
public interface Animal {
void user();
}
//定义狗
@Component
public class Dog implements Animal {
@Override
public void user() {
System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");
}
}
//定义年轻人
@Component
public class YoungPerson implements Person {
@Autowired
private Animal animal = null;
@Override
public void service() {
this.animal.user();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
//定义猫
@Component
public class Cat implements Animal{
@Override
public void user() {
System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");
}
}
//定义配置类
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和类都在restart下
public class AppConfig {
}
此时没有配置lazyInit的情况进行断点测试如下:
可以看到在断点处,我们并没有获取Bean 的实例,而日志就已经打出了,可见它是在SpringIoC容器初
始化时就执行了实例化和依赖注入。为了改变这个情况,我们在配置类AppConfig的@ComponentScan
中加入lazylnit 配置,如下面的代码:
@Configuration
@ComponentScan(value = "com.dragon.restart",lazyInit = true)
public class AppConfig {
}
就可以发现在断点处“延迟依赖注入”这行并不会出现在日志中,只有运行过断点处才会出现这行日志,这是因为我们把它修改为了延迟初始化, Spring并不会在发布Bean定义后马上为我们完成实例化和依赖注入。
如果仅仅是实例化和依赖注入还是比较简单的,还不能完成进行自定义的要求。 为了完成依赖注入的功能, Spring 在完成依赖注入之后,还提供了一系列的接口和配置来完成Bean初始化的过程,让我们学习这个过程。 Spring在完成依赖注入后,还会进行如下图所示流程来完成它的生命周期:
图中描述的是整个IoC容器初始化Bean 的流程,作为开发者,需要注意这些流程。除此之外,还需要注意以下两点:
- 这些接口和方法是针对什么而言的。 对于上图, 在没有注释的情况下的流程节点都是针对单个Bean 而言的,但是BeanPostProcessor 是针对所有 Bean 而言的,这是我们需要注意的地方。
- 即使你定义了 ApplicationContextAware 接口,但是有时候并不会调用,这要根据你的 IoC 容器来决定。 我们知道, Spring IoC 容器最低的要求是实现 BeanFactory 接口,而不是实现ApplicationContext 接口 。 对于那些没有实现 ApplicationContext 接口的容器,在生命周期对应的ApplicationContextAware 定义的方法也是不会被调用的,只有实现了 ApplicationContext 接口的容器,才会在生命周期调用 ApplicationContextAware 所定义的 setApplicationContext方法。
现在改造一下YoungPerson类:
@Component
public class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private Animal animal = null;
@Override
public void service() {
this.animal.user();
}
@Autowired
@Qualifier("dog")
@Override
public void setAnimal(Animal animal) {
System.out.println("延迟依赖注入");
this.animal = animal;
}
@Override
public void setBeanName(String name) {
System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");
}
@Override
public void destroy() throws Exception {
System.out.println ("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println ("【" + this.getClass().getSimpleName() + "】调用InitializingBean方法的afterPropertiesSet方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println ("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware方法的setApplicationContext方法");
}
@PostConstruct
public void init () {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");
}
@PreDestroy
public void destroyl () {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");
}
}
这样,这个 B巳an 就实现了生命周期中单个 Bean 可以实现的所有接口, 并且通过注解@PostConstruct 定义了初始化方法,通过注解@PreDestroy 定义了销毁方法。 为了测试 Bean 的后置处理器, 这里创建一个类BeanPostProcessorExampIe,如下:
/**
* @Version: 1.0.0
* @Author: Dragon_王
* @ClassName: BeanPostProcessorExample
* @Description: TODO描述
* @Date: 2024/1/20 23:34
*/
public class BeanPostProcessorExample implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用"+
"postProcessBeforeinitialization方法,参数【"+
bean.getClass().getSimpleName()+"】【"+beanName+"】");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用"+
"postProcessAfterinitialization方法,参数【"+
bean.getClass().getSimpleName()+"】【"+beanName+"】");
return bean;
}
}
注意,这个Bean后置处理器将对所有的Bean有效,运行测试如下:
测试类:
AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;
ctx.close();
2024-01-20T23:43:23.135+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : Starting RestartApplicationTests using Java 19 with PID 748 (started by ThundeRobot in E:IDEA_projectsrestart)
2024-01-20T23:43:23.136+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : No active profile set, falling back to 1 default profile: "default"
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Dog】【dog】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Dog】【dog】
延迟依赖注入
【YoungPerson】调用BeanNameAware的setBeanName
【YoungPerson】调用BeanFactoryAware的setBeanFactory
【YoungPerson】调用ApplicationContextAware方法的setApplicationContext方法
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【YoungPerson】【youngPerson】
【YoungPerson】注解@PostConstruct定义的自定义初始化方法
【YoungPerson】调用InitializingBean方法的afterPropertiesSet方法
BeanPostProcessor调用postProcessAfterinitialization方法,参数【YoungPerson】【youngPerson】
BeanPostProcessor 调用 postProcessBeforeinitialization 方法,参数 【Cat】【cat】
BeanPostProcessor 调用 postProcessAfterinitialization 方法, 参数 【Cat】【cat】
2024-01-20T23:43:24.044+08:00 INFO 748 --- [main] c.d.restart.RestartApplicationTests : Started RestartApplicationTests in 1.142 seconds (process running for 1.772)
【YoungPerson】注解@PreDestroy定义的自定义销毁方法
【YoungPerson】调用DisposableBean方法
从日志可以看出,对于Bean后置处理器(BeanPostProcessor)而言, 它对所有的 Bean 都起作用,而其他的接口则是对于单个Bean起作用。我们还可以注意到BussinessPerson执行的流程是上图所画出的流程。有时候Bean 的定义可能使用的是第三方的类,此时可以使用注解@Bean来配置自定义初始化和销毁方法,如下所示:
@Bean(InitMethod =”Init”, destroyMethod = ”destroy” )
二、作用域
在介绍IoC 容器最顶级接口 BeanFactory 的时候, 可以看到 isSingleton 和 isPrototype 两个方法。其中,isSingleton 方法如果返回 true,则 Bean 在 loC 容器中以单例存在,这也是 Spring IoC 容器的默认值;如果 isPrototype 方法返回 true,则当我们每次获取 Bean 的时候, IoC 容器都会创建一个新的 Bean,这显然存在很大的不同,这便是Spring Bean 的作用域的问题。在一般的容器中, Bean都会存在单例(Singleton)和原型(Prototype)两种作用域, Java EE 广泛地使用在互联网中,而在 Web容器中, 则存在页面(page)、请求(request)、会话 (session)和应用(application) 4 种作用域。对于页面(page),是针对 JSP 当前页面的作用域,所以 Spring是无法支持的。为了满足各类的作用域,在Spring 的作用域中就存在如表所示的几种类型。
作用域类型 | 使用范围 | 作用域描述 |
---|---|---|
singleton | 所有Spring 应用 | 默认值, loC 容器只存在单例 |
prototype | 所有Spring 应用 | 每当从IoC 容器中取出一个 Bean,则创建一个新的Bean |
session | Spring Web 应用 | HTTP 会话 |
application | Spring Web 应用 | Web 工程生命周期 |
request | Spring Web 应用 | Web 工程单次请求 (request) |
globalSession | Spring Web 应用 | 在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用 |
- 前四个最常用
- 对于application作用域,完全可以使用单例来替代。
下面我们探讨单例 (Singleton)和原型(prototype)的区别
首先定义一个类
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean { }
这是一个简单的类, 可以看到这里声明作用域的代码已经被注释掉了, 这样就是启用默认的作用域,实际就是单例。为了证明作用域的存在,我们进行一下测试:
AnnotationConfigApplicationContext ctx
=new AnnotationConfigApplicationContext (AppConfig.class);
ScopeBean scopeBeanl = ctx.getBean (ScopeBean.class);
ScopeBean scopeBean2 = ctx.getBean (ScopeBean .class);
System.out.println (scopeBeanl == scopeBean2) ;
从测试的结果来看,显然scopeBeanl 和 scopeBean2 这两个变量都指向了同一的实例,所以在IoC容器中, 只有一个ScopeBean 的实例。 然后取消代码中作用域代码的注释,进行同样的测试, 则可以看到scopeBeanl == scopeBean2 返回的将是 false,而不再是 true, 那是因为我们将Bean 的作用域修改为了 prototype,这样就能让IoC 容器在每次获取Bean 时,都新建一个Bean的实例返回给调用者。
这里的 ConfigurableBeanFactory 只能提供单例 ( SCOPE_ SINGLETON )和原型 ( SCOPE_PROTOTYPE)两种作用域供选择, 如果是在 SpringMVC环境中,还可以使用 WebApplicationContext去定义其他作用域, 如请求(SCOPE REQUEST)、 会话 (SCOPE_SESSION) 和应用 (SCOPE_APPLICATION)。 例如,下面的代码就是定义请求作用域:
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean { }
总结
以上就是Bean生命周期和作用域的讲解。