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

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


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

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

相关推荐
明月看潮生36 分钟前
青少年编程与数学 02-011 MySQL数据库应用 03课题、客户端工具
数据库·mysql·青少年编程·编程与数学
4647的码农历程40 分钟前
MySQL -- 复合查询
数据库·mysql·oracle
新停浊酒杯41 分钟前
基于Spring Boot3 Spring Cloud2023 Spring Cloud Alibaba2023对外提供一个分布式微服务最新基础示例模板
spring boot·spring cloud·微服务
小小鸭程序员2 小时前
Spring Boot 整合 Redis 使用教程
java·spring boot·redis·mysql·spring
云空5 小时前
《解锁Netlify:静态网站托管》:此文为AI自动生成
linux·服务器·网络·数据库
秋野酱5 小时前
基于javaweb的SpringBoot个人健康管理系统小程序微信小程序设计与实现(源码+文档+部署讲解)
spring boot·微信小程序·小程序·毕业设计
数据知道5 小时前
数据库:一文掌握 PostgreSQL 的各种指令(PostgreSQL指令备忘)
数据库·sql·postgresql
落魄实习生5 小时前
ELK(Elasticsearch、Logstash、Kbana)安装及Spring应用
spring·elk·elasticsearch
是小蟹呀^8 小时前
uni-app+SpringBoot: 前端传参,后端如何接收参数
spring boot·uni-app
Dnui_King8 小时前
Redis 持久化机制:AOF 与 RDB 详解
数据库·redis