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

前言

???本期讲解关于spring原理Bean的相关知识介绍~~~

??感兴趣的小伙伴看一看小编主页:-CSDN博客

?? 你的点赞就是小编不断更新的最大动力

??那么废话不多说直接开整吧~~

目录

???1.Bean的作用域

??1.1概念

??1.2Bean的作用域

??1.3代码演示

???2.Bean的生命周期

??2.1概念以及分类

??2.2代码演示

??2.3原码阅读

2.3.1解析Bean类

2.3.2实例化前处理

2.3.3创建Bean的实例

2.3.4初始化Bean

???3.总结

**??**1.Bean的作用域

??1.1概念

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

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

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

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

如下代码所示:

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

复制代码
public class Dog {
    public String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

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

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

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

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

}

当然这小编是在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 作域

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

Bean的6种作用域

singleton

每个Spring IoC容器内同名称的bean只有个实例(单例)(默认

prototype

每次使该bean时会创建新的实例(单例)

request

每个HTTP 请求命周期内, 创建新的实例

session

每个HTTP Session命周期内, 创建新的实例

Application

每个ServletContext命周期内, 创建新的实例

websocket

每个WebSocket命周期内, 创建新的实例

??1.3代码演示

以下就是代码演示:

复制代码
@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;
    }
}

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

@Scope(value =WebApplicationContext.SCOPE_REQUEST, proxyMode =

ScopedProxyMode.TARGET_CLASS)

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

测试代码如下所示:

复制代码
@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;
    }
}

这里需要注意的是,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代码演示

代码如下所示:

复制代码
@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方法");
    }
}

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

日志打印如下:

??2.3原码阅读

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

找到AbstractAutowireCapableBeanFactory类里createBean方法的实现

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

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

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

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

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

2.3.3创建Bean的实例

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

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

            return beanInstance;

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的生命周期的实现过程;

???~~~~最后希望与诸君共勉,共同进步!!!


???以上就是本期内容了, 感兴趣的话,就关注小编吧。

???期待你的关注~~~

相关推荐
paopaokaka_luck12 分钟前
基于SpringBoot+Uniapp的非遗文化宣传小程序(AI问答、协同过滤算法、Echarts图形化分析)
java·vue.js·spring boot·后端·学习·小程序·uni-app
Java初学者小白18 分钟前
秋招Day17 - Spring - 事务
java·数据库·spring
NineData26 分钟前
NineData新增SQL Server到MySQL复制链路,高效助力异构数据库迁移
数据库·人工智能·mysql
程序员阿明40 分钟前
netty的编解码器,以及内置的编解码器
java·spring boot
CodeWolf2 小时前
如何自定义一个起步依赖starter
spring boot·spring
极限实验室2 小时前
TDBC 2025 大会聚焦 AI 与数据库融合,极限科技发布新一代 Coco AI 搜索平台
数据库·人工智能
一只叫煤球的猫2 小时前
这些 Spring Boot 默认配置不改,迟早踩坑
java·spring boot·后端
AWS官方合作商2 小时前
驾驭云端算力:在AWS上构建高性能计算(HPC)集群的完整解决方案
数据库·云计算·aws
泉城老铁3 小时前
千万级数据MySQL的极致优化
大数据·数据库·mysql
发仔1233 小时前
Neo4j 在 Spring Boot 中的使用详解
数据库·spring boot