【Spring Boot】Spring 魔法世界:Bean 作用域与生命周期的奇妙之旅

📚️1.Bean的作用域

🚀1.1概念

在Spring IoC&DI阶段, 我们学习了Spring是如何帮助我们管理对象的.

  1. 通过 @Controller , @Service , @Repository , @Component , @Configuration ,@Bean 来声明Bean对象.

  2. 通过 ApplicationContext 或者 BeanFactory 来获取对象

  3. 通过 @Autowired , Setter ⽅法或者构造⽅法等来为应⽤程序注⼊所依赖的Bean对象

如下代码所示:

首先我们在model层定义一个实体类:

typescript 复制代码
public class Dog {
    public String name;
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
AI写代码java
运行

然后我们在config层通过@Bean将对象交给spring帮我们进行管理:

java 复制代码
@Configuration
public class DogBeanConfig {
    //使用@Bean将对象交给spring进行管理
    @Bean
    public Dog dog(){
        Dog dog = new Dog();
        dog.setName("wangcai");
        return dog;
    }
}
AI写代码java
运行

那么在后面我们可以从spring容器中获取得到这里对象;

java 复制代码
@SpringBootTest
class SpringPrincipleApplicationTests {
    @Autowired
    ApplicationContext context;
    @Test
    void contextLoads() {
        //单列模式
        Dog dog = context.getBean("dog", Dog.class);
        System.out.println(dog);
    }
 
}
AI写代码java
运行

当然这小编是在test包中进行测试使用的,所以需要注入applicationcontext类,获取spring上下文(获取spring容器),再拿到这里对象;

此时我们再次拿对象,然后进行两次对象获取打印对应的地址发现:

地址是一样的, 说明每次从Spring容器中取出来的对象都是同⼀个.这也是"单例模式"

单例模式: 确保⼀个类只有⼀个实例,多次创建也不会创建出多个实例

默认情况下, Spring容器中的bean都是单例的, 这种⾏为模式, 我们就称之为Bean的作⽤域

所以bean的作用域概念就是:

Bean的作用域是指在spring框架中一种行为模式

单例作用域表示全局只有一份,他是全局共享的,若进行了修改,那么再次获取次对象的某个属性就是被修改过后的属性;

但是如何再次访问时,如何重新创建一个对象呢,那么这就是其他的作用域了;

🚀1.2Bean的作用域

Bean的作用域分为6种,如下所示:

  1. singleton:单例作⽤域

  2. prototype:原型作⽤域(多例作⽤域)

  3. request:请求作⽤域

  4. session:会话作⽤域

  5. Application: 全局作⽤域

  6. websocket:HTTP WebSocket 作⽤域

这六种作用域的大致作用意义如下表所示:

singleton 每个Spring IoC容器内同名称的bean只有⼀个实例(单例)(默认
prototype 每次使⽤该bean时会创建新的实例(⾮单例)
request 每个HTTP 请求⽣命周期内, 创建新的实例
session 每个HTTP Session⽣命周期内, 创建新的实例
Application 每个ServletContext⽣命周期内, 创建新的实例
websocket 每个WebSocket⽣命周期内, 创建新的实例

🚀1.3代码演示

以下就是代码演示:

java 复制代码
@Configuration
public class DogBeanConfig {
    //使用@Bean将对象交给spring进行管理
    @Bean
    public Dog dog(){
        Dog dog = new Dog();
        dog.setName("wangcai");
        return dog;
    }
 
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Dog singletonDog(){
        Dog dog =new Dog();
        return dog;
    }
 
    //原型作用域
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog prototypeDog(){
        Dog dog =new Dog();
        return dog;
    }
    //请求作用域
    @Bean
    @RequestScope
    public Dog requestDog(){
        Dog dog =new Dog();
        return dog;
    }
 
    //会话作用域
    @Bean
    @SessionScope
    public Dog sessionDog(){
        Dog dog =new Dog();
        return dog;
    }
 
    @Bean
    @ApplicationScope
    public Dog applicationDog(){
        Dog dog =new Dog();
        return dog;
    }
}
AI写代码java
运行

其中单列作用域,与多列作用域(原型作用域)使用@scope注解,其余作用域使用对应名字的注解,内部等于

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode =

ScopedProxyMode.TARGET_CLASS)

其中黄色标注部分是可以进行对应作用域的更改的; 其中 proxyMode为spring bean设置代理,表示bean是基于CGLIB进行动态代理的;

测试代码如下所示:

kotlin 复制代码
@RestController
@RequestMapping("/test")
public class DogBeanController {
    @Autowired
    private ApplicationContext context;
    @Autowired
    private Dog singletonDog;
    @Autowired
    private Dog prototypeDog;
    @Autowired
    private Dog requestDog;
    @Autowired
    private Dog sessionDog;
    @Autowired
    private Dog applicationDog;
    /**
     * 通过比较不同作用域,spring容器启动直接注入,后不会进行改变(原型作用域)
     * @return
     */
 
    @RequestMapping("/single")
    public String single(){
        //从context获取对象
        Dog contexDog = context.getBean("singletonDog",Dog.class);
        return "contextDog:"+contexDog +",autowiredDog:"+singletonDog;
    }
    @RequestMapping("/prototype")
    public String prototype(){
        //从context获取对象
        Dog contexDog = context.getBean("prototypeDog",Dog.class);
 
        return "contextDog:"+contexDog +",<br/> autowiredDog:"+prototypeDog;
    }
 
 
    @RequestMapping("/request")
    public String request(){
        //从context获取对象
        Dog contexDog = context.getBean("requestDog",Dog.class);
        return "contextDog:"+contexDog +",<br/> autowiredDog:"+requestDog;
    }
 
    @RequestMapping("/session")
    public String session(){
        //从context获取对象
        Dog contexDog = context.getBean("sessionDog",Dog.class);
        return "contextDog:"+contexDog +",<br/> autowiredDog:"+sessionDog;
    }
 
    @RequestMapping("/application")
    public String application(){
        //从context获取对象
        Dog contexDog = context.getBean("applicationDog",Dog.class);
        return "contextDog:"+contexDog +",<br/> autowiredDog:"+applicationDog;
    }
}
AI写代码java
运行

这里需要注意的是,autowired直接注入是在spring容器启动时就进行注入,Applicationcontext获取容器注入是每次请求才注入对象;

所以具体的请求情况如下所示:

对于单列来说,对象是共享的,所以注入的对象地址两个都是一样的;

对于原型作用域:

由于每次请求使用bean时都会创建新的实例,所以多次请求会进行变化,spring启动时就已经注入,所以不会进行变化(除非重启服务);

对于请求作用域:

因为每次请求都会重新创建实例,所以不断刷新后对象地址就会进行改变;

对于会话作用域:

对于会话作用域,范围比请求作用域更加广泛,在一个浏览器上算是一个会话,如果要进行改变对象地址,就得重新开启一个会话,那么可以使用两个浏览器进行url的请求访问,那么此时不同浏览器的对象地址就是不一样的;

最后一个应用作用域,小编就不再进行演示了,结果和单例一样;

Application scope就是对于整个web容器来说, bean的作⽤域是ServletContext级别的. 这个和

singleton有点类似,区别在于: Application scope是ServletContext的单例, singleton是⼀个

ApplicationContext的单例. 在⼀个web容器中ApplicationContext可以有多个

📚️2.Bean的生命周期

🚀2.1概念以及分类

⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程, 我们把这个过程就叫做⼀个对象的⽣命周期.

Bean 的⽣命周期分为以下5个部分:
1. 实例化(为Bean分配内存空间)

2. 属性赋值(Bean注⼊和装配, ⽐如 @AutoWired )

3. 初始化

a. 执⾏各种通知, 如 BeanNameAware , BeanFactoryAware ,ApplicationContextAware 的接口方法.

b. 执⾏初始化⽅法

▪ xml定义 init-method

▪ 使⽤注解的⽅式 @PostConstruct

▪ 执⾏初始化后置⽅法( BeanPostProcessor )

4. 使⽤Bean

5. 销毁Bean

🚀2.2代码演示

代码如下所示:

typescript 复制代码
@Component
public class BeanLifeComponent implements BeanNameAware {
    public Dog singletonDog;
 
    public BeanLifeComponent() {
        System.out.println("执行构造函数...");
    }
    //属性注入
    @Autowired
    public void setSingletonDog(Dog singletonDog) {
        this.singletonDog = singletonDog;
        System.out.println("执行setSingletonDog....");
    }
    
    //执行各种通知
    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName: "+name);
    }
    //初始化方法
    @PostConstruct
    public void init(){
        System.out.println("执行PostConstruct...");
    }
    //使用bean
    public void use(){
        System.out.println("执行use方法....");
    }
    //销毁bean
    @PreDestroy
    public void destroy(){
        System.out.println("执行destroy方法");
    }
}
AI写代码java
运行

对应就是,实例化分配内存,然后属性注入,在进行初始化(各种通知,以及通过注解初始化方法,最后使用bean,以及销毁bean)

日志打印如下:

🚀2.3原码阅读

首先点击进入AbstractAutowireCapableBeanFactory类查看源码内容:

找到AbstractAutowireCapableBeanFactory 类里 createBean 方法的实现

此方法主要在创建过程中,会先进行一些准备工作,比如解析 Bean 的类,还会尝试在实例化之前进行一些处理,若有合适的处理器处理并返回了 Bean 实例,就直接返回;若没有,则调用 doCreateBean 方法实际创建 Bean 实例。

2.3.1解析Bean类
ini 复制代码
 Class<?> resolvedClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
        if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
            mbdToUse = new RootBeanDefinition(mbd);
            mbdToUse.setBeanClass(resolvedClass);
AI写代码java
运行

this.resolveBeanClass(mbd, beanName, new Class0):尝试解析 Bean 定义对应的类。若解析成功,且 Bean 定义里没有直接设置类,同时 Bean 类名不为空,就创建一个新的 RootBeanDefinition 对象 mbdToUse,并把解析后的类设置进去。

2.3.2实例化前处理
kotlin 复制代码
       try {
            beanInstance = this.resolveBeforeInstantiation(beanName, mbdToUse);
            if (beanInstance != null) {
                return beanInstance;
            }
AI写代码java
运行

这里的resolveBeforeInstantiation会调用后至处理器的方法,在执行后,判断如果该实例为非空的,那么直接返回这个实例,不再执行下面实例化的创建过程;

2.3.3创建Bean的实例

在上述解析和实例化处理后,就开始执行实例的创建工作了,主要的方法就是doBeanCreate;

kotlin 复制代码
        try {
            beanInstance = this.doCreateBean(beanName, mbdToUse, args);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Finished creating instance of bean '" + beanName + "'");
            }
 
            return beanInstance;
AI写代码java
运行

this.doCreateBean(beanName, mbdToUse, args):调用该方法实际创建 Bean 实例,这个方法包含了实例化、属性注入、初始化等一系列操作。

点开doCreateBean方法;

在此方法中:

这三个⽅法与三个⽣命周期阶段⼀⼀对应

  1. createBeanInstance() -> 实例化
  2. populateBean() -> 属性赋值
  3. initializeBean() -> 初始化

点击进入initializeBean();

2.3.4初始化Bean

initializeBean 方法的主要作用是对已经实例化的 Bean 进行初始化操作,包括调用 Aware 接口方法、应用 BeanPostProcessor 前置处理、调用初始化方法以及应用 BeanPostProcessor 后置处理。这里可以对照英文单词看看;

其中初始化方法如下:

invokeAwareMethods 方法用于检查 Bean 是否实现了特定的 Aware 接口,若实现了就判断是否实现了特定的子接口;例如实现了BeanNameAware接口,调用相应的 setter 方法,将相关信息注入到 Bean 中。

大致的思维导图就是:

📚️3.总结

本期主要讲解了Spring原理中的Bean的作用域以及生命周期,通过概念以及相关代码进行演示,最后深入源码讲解Bean的生命周期的实现过程;

相关推荐
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull3 小时前
Spring Boot请求处理组件对比详解
java·spring boot
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
不能只会打代码3 小时前
Day 011 — Spring 全家桶深度拆解
spring boot·mybatis·spring aop·spring mvc·spring ioc
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金3 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛4 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构