Spring 中 Bean 的作用域和生命周期

news2025/8/8 10:57:06

目录

1. Bean 的作用域

1.1 Bean 的六大作用域

1.1.1 单例作用域 (singleton)

1.1.2 原型作用域 (prototype)

1.1.3 请求作用域 (request)

1.1.4 会话作用于 (session)

1.1.5 全局作用于 (application)

1.1.6 HTTP WebSocket 作用域 (websocket)

1.2 如何设置 Bean 的作用域

1.2.1 直接设置: @Scope("prototype")

1.2.2 使用类似枚举的方式设置 Bean 的作用域

2. Spring 的主要执行流程

3. Bean 的生命周期


1. Bean 的作用域

什么是 Bean 的作用域 ? 我们以前所谈到的作用域就是指程序中变量的可用范围, 例如局部变量的作用域, 就是出了函数就不能用了. 而此处 Bean 的作用域是指在 Spring 中的某种行为模式. 下面通过一个示例来演示 Bean 的作用域.

【代码示例】

Cat 类:

public class Cat {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

使用方法注解存储 Cat: 

@Controller
public class CatBean {
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setId(1);
        cat.setName("加菲猫");
        cat.setAge(12);
        return cat;
    }
}

张三的业务: 

@Controller
public class ScopeController {
    @Autowired
    private Cat cat1;

    public void doController() {
        System.out.println("do scope controller");
        System.out.println("原数据: " + cat1.toString());
        cat1.setName("汤姆猫"); // 张三想要修改自己的 cat
        System.out.println("新数据: " + cat1.toString());
    }
}

李四的业务: 

@Controller
public class ScopeController2 {
    @Autowired
    private Cat cat2;

    public void doController() {
        System.out.println("do scope controller 2");
        System.out.println(cat2.toString()); // 李四想要拿到 Spring 中的加菲猫
    }
}

执行启动类: 

public class App {
    public static void main(String[] args) {
         // 1. 得到 Spring 上下文
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        // 执行张三的代码
        ScopeController scopeController =
                context.getBean("scopeController", ScopeController.class);
        scopeController.doController();
        System.out.println("=====================================");
        // 执行李四的代码
        ScopeController2 scopeController2 =
                context.getBean("scopeController2", ScopeController2.class);
        scopeController2.doController();
    }
}

运行结果: 

【问题】

从上述示例中分析: 站在张三的角度上, 他只是想要修改自己的 Cat,  将 name 修改为 "汤姆猫", 一打印原数据和新数据都没问题, 而这时李四通过属性注入的方式, 调用 cat 的 toString() 方法, 想要拿到一只 "加菲猫", 却打印出来一只 "汤姆猫", 这就是上述代码示例想要表达的一个问题.

为什么会出现这种情况呢 ?

这里就牵扯到了 Bean 的作用域, Spring 中存储的对象的默认作用域就是单例作用域, 所以就会导致张三觉得自己只修改了自己的 cat1 对象, 并没有动原数据, 却让李四拿到了自己修改后的数据. 实际上 Spring 中只有一份 bean 对象, 而 cat1 和 cat2 的引用都指向这一份 bean , 所以张三对 cat1 的行为就会影响到 Spring 中这一份 bean 的状态.

上述示例就是为了讲明白 Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式, 比如上述的 "单例作用域" (singleton), 就表示 Bean 在整个 Spring 中只有一份, 它是全局共享的, 如果有一个人注入了 bean 对象, 并对其进行了修改, 那么其他人再去注入得到时候, 就都是修改后的数据.

1.1 Bean 的六大作用域

1. singleton:单例作⽤域
2. prototype:原型作⽤域(也叫多例作⽤域)
3. request:请求作⽤域
4. session:会话作⽤域
5. application:全局作⽤域
6. websocket:HTTP WebSocket 作⽤域
前两种作用域是在普通的 Spring 项目中使用, 后四种作用域存在于 Spring MVC 项目中.(前四种要知道)

1.1.1 单例作用域 (singleton)

含义: 单例作用域是指在 Spring IoC 容器中只存储一份, 也就是说只有一个实例, 无论我们是通过 @Autowried, @Resource 去获取, 还是通过上下文对象去 getBean(), 拿到的 bean 对象都是同一份. (并且单例作用域是 Spring 中默认的作用域)

场景:: 通常是无状态的 Bean 使用的作用域. (无状态表示 Bean 对象的属性状态不需要修改)

1.1.2 原型作用域 (prototype)

含义: 原型作用域也叫作多例作用域, 每次从 Spring 中获取 Bean 对象, 都会创建一份新的实例, @Autowired, @Resource 注入的对象以及 context 上下文 getBean 拿到的都是不同的 bean 对象.

场景: 通常是有状态的 Bean 使用的作用域 (有状态表示 Bean 对象的属性需要被修改)

1.1.3 请求作用域 (request)

含义: 每一次 HTTP 请求都会创建新的实例, 类似于 prototype. 

场景: 一次 HTTP 的请求和响应共享一个 bean. (仅在 Spring MVC 中使用)

1.1.4 会话作用于 (session)

含义:  在一个 HTTP session 中,  定义一个 Bean 实例.

场景:  同一个用户的会话共享 Bean (例如在登录场景中记录一个用户的登录信息) 仅在 Spring MVC 中使用

1.1.5 全局作用于 (application)

含义: 在一个 HTTP Servlet Context 中,  定义一个 Bean 实例

场景: Web 应用的上下文信息, 记录一个应用的共享信息.(仅在 Spring MVC 中使用)

application 作用域和 单例作用域还是有区别的, 它只是同一份上下文对象共享同一个 bean, 当再次创建上下文对象时, 调用 getBean() 就是另一个 Bean 对象了.

1.1.6 HTTP WebSocket 作用域 (websocket)

含义: 在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例

场景: WebSocket的每次会话中,保存了⼀个 Map 结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。(仅在 Spring MVC 中使用)

1.2 如何设置 Bean 的作用域

既然我们知道了 Bean 有六大作用域, 那我们应该如何设置 Bean 的作用域呢 ? 使用 @Scope 标签

设置 Bean 的作用域有两种方式:

  • 直接设置: @Scope("prototype")
  • 使用类似枚举的方式设置: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

1.2.1 直接设置: @Scope("prototype")

使用前面猫的例子: 

【代码示例】

@Controller
public class CatBean {
    @Scope("prototype")
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setId(1);
        cat.setName("加菲猫");
        cat.setAge(12);
        return cat;
    }
}

我们只需要在前面的代码中的 CatBean 类中的 @Bean 注解上加上一个 @Scope 注解, 并设置 "prototype" , 此时我们运行程序, 如果李四拿到的是 "加菲猫", 那么就说明此时是多例作用域.

运行结果: 由此可见此时的作用域就是多例作用域.

1.2.2 使用类似枚举的方式设置 Bean 的作用域

依旧使用前面猫的例子: 

【代码示例】

@Controller
public class CatBean {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setId(1);
        cat.setName("加菲猫");
        cat.setAge(12);
        return cat;
    }
}

此时李四还是能拿到原来的 "加菲猫", 也是成功的将 Bean 的作用域修改成了多例作用域.

2. Spring 的主要执行流程

 主要执行流程: 

1. 启动 Spring 容器

2. 初始化 Bean 【加载】

3. 将Bean 对象注入到容器中

4. 使用 Bean

最后其实还有销毁 Bean.

3. Bean 的生命周期

1. 实例化 Bean (不等于初始化) 【分配内存空间】

2. 设置属性【依赖注入DI】

3. Bean 的初始化

  • 执行各种通知
  • 初始化的前置方法. (以前是通过在 xml 中配置 init-method() 方法, 之后改用 @PostConstruct 注解)
  • 初始化方法
  • 初始化的后置方法

4. 使用 Bean

5.销毁 Bean. (以前通过 xml 的 destroy-method, 之后改用 @PreDestroy 注解)

下面通过代码的方法来观察 Bean 的生命周期: 

【代码示例】

public class BeanLifeController implements BeanNameAware {
    @Override
    public void setBeanName(String s) {
        System.out.println("执行各种通知:" + s);
    }

    /**
     * xml 中 init-method 指定的前置方法
     */
    public void initMethod() {
        System.out.println("执行 init-method 前置方法");
    }

    /**
     * 改用注解后的前置方法
     */
    @PostConstruct
    public void PostConstruct() {
        System.out.println("执行 PostConstruct 前置方法");
    }

    /**
     * 销毁前执行方法
     */
    @PreDestroy
    public void PreDestroy() {
        System.out.println("执行 PreDestroy 销毁方法");
    }

    public void use() {
        System.out.println("使用 bean - 执行 use 方法");
    }
}

使用原始的 <bean> 标签设置 bean

<bean id="beanLife" class="com.hl.controller.BeanLifeController"
          init-method="initMethod"></bean>

启动类: 

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        // 根据 id 获取 bean 对象
        BeanLifeController controller =
                context.getBean("beanLife", BeanLifeController.class);
        // 使用 bean
        controller.use();
        // 销毁 bean
        context.destroy();
    }
}

执行结果: 

1. 从代码的运行结果来看, 大致执行顺序还是一致的, init-method() 方法和 postConstruct() 方法的执行先后, 可以理解使用注解的方式是改进后的, 优先级被提高了.

2. 这几个生命周期可以这样理解, 方便我们记住: 

  • 实例化 Bean  --> 【买房】
  • 设置属性 -->  【装修】
  • Bean 的初始化 --> 【买家电: 桌子, 凳子, 冰箱, 空调......】
  • 使用 Bean --> 【入住】
  • 销毁 Bean -->【不想住了, 卖房】

【问题】为什么【依赖注入DI】的执行时机要在 【Bean 的初始化之前】?

public class BeanLifeController implements BeanNameAware {

    // 依赖注入DI
    @Autowired
    private UserService userService;

    @Override
    public void setBeanName(String s) {
        System.out.println("执行各种通知:" + s);
    }

    // 初始化的前置方法
    @PostConstruct
    public void PostConstruct() {
        // 在初始化的前置方法中调用
        userService.doUserService();
        System.out.println("执行 PostConstruct 前置方法");
    }
}

上述代码在初始化的前置方法中使用注入的 Bean, 如果是先初始化 Bean,  就会导致空指针异常, 我初始化方法中需要使用到注入的 Bean , 那么一定是先执行【依赖注入】, 在执行【初始化】。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/34366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

142.如何个性化推荐系统设计-2

142.1 离线训练 离线训练流程 如何线上实时反馈特征&#xff1f; 在线计算&#xff0c;与曝光日志一起上报&#xff0c;离线直接使用 如何解决曝光不足问题&#xff1f; 使用CTR的贝叶斯平滑&#xff08;CTR 曝光次数 / 点击次数&#xff09; 所有新闻自身CTR服从Beta分布: 某…

199道SpringCloud面试题,你能答上来吗

前言 Spring Cloud Alibaba 是阿里中间件团队主导的一个新生项目&#xff0c;正处于高速迭代中。 其次&#xff0c;对于中国用户来说&#xff0c;Spring Cloud Alibaba 还有一个非常特殊的意义&#xff1a;它将曾经红极一时的 Dubbo&#xff0c;以及阿里巴巴的强力消息中间件…

每天花2小时复习Java面试指南,高级架构视频,我进了阿里定级P7

Java进阶架构师必备 基础 容器 并发 JVM Java8 计算机网络 计算机操作系统 Linux 数据结构 算法 mysql (优化思路) 系统设计 分布式 线上问题调优(虚拟机&#xff0c;tomcat) 面试指南 工具 ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&…

用Python采集球员信息,成功预测到了球赛胜负?

前言 嗨嗨&#xff0c;最近看球赛的朋友多吗 emm怎么说&#xff0c;我对这个虽然兴趣不是很大 但是还是想跟朋友赌赌&#xff0c;自己对这些球员也不是很熟悉&#xff0c;索性叫我的好同事帮我用Python采集了各国球员的一些信息&#xff0c;没料到竟预测成功了&#xff01; …

2022 谷歌出海创业加速器展示日: 见证入营企业成长收获

经历三个月的沉淀&#xff0c;迎来了展示日的大放异彩。10 家入营企业的路演分享&#xff0c;带来诸多启发 ——企业出海有什么挑战和难点&#xff1f;加入谷歌出海创业加速器&#xff0c;团队有哪些收获&#xff1f;三个月的培训和交流&#xff0c;带来了怎样的感受&#xff1…

Vue中插槽slot

slot插槽&#xff08;别名&#xff1a;内容分发&#xff09;&#xff1a; 作用&#xff1a; 混合父组件的内容与子组件自己的模板&#xff1b;父组件模板内容在父组件作用域内编译&#xff1b;子组件模板的内容在子组件作用域内编译&#xff1b;扩展组件能力&#xff0c;提高…

VGG16 -19 — CV 中表现最好的 ConvNet 模型

从先进的计算机视觉出现的 Alexnet 开始&#xff0c;人们开始尝试不同的架构。牛津大学工程科学系的 Karen simonyan 和 Andrew Zisserman 在对 ImageNet Challenge 2014 的数据集进行了一些实验后提出了非常深的卷积网络&#xff1a;VERY DEEP CONVOLUTIONAL NETWORKS FOR LAR…

美食杰项目(二)首页

目录前言具体样式代码思路加载样式相应组件相应代码总结&#xff1a;前言 本节给大家讲的是美食杰项目的首页的主要功能和具体样式 具体样式 代码思路 1.点击首页跳转到首页页面 2.在父组件将轮播图所需的图片请求出来&#xff0c;再传给轮播组件 3.在父组件将商品列表的数据…

找不到工作,软件测试真的不香了?

最近总是有人说测试先不要干&#xff0c;测试不好找工作。测试没有以前那么香了&#xff1f; 可是&#xff0c;这是真的么&#xff1f;什么样的人会说这样的话&#xff1f; 肯定不是现在还在岗的人说的&#xff0c;也不是已经拿到企业聘用offer的人说的。 因为他们都是优秀的…

交换机的工作原理以及搭建局域网划分VLAN

作者简介&#xff1a;一名99年软件运维应届毕业生&#xff0c;正在自学云计算课程。宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。创作不易&#xff0c;动动小手…

OneAuth 2022.11.23版本更新内容

2022.11.23版本更新内容&#xff1a; 新增IdP飞书 云目录增加对Group的支持GWA浏览器插件适配性优化自定义授权服务器优化&#xff0c;适应RBAC、ABAC等多种场景授权IdP 北森优化&#xff0c;适配自定义的属性租户的部分试用功能需要联系后台开通其他一些Bug的修复 标题新增 …

GIT

X.1 git上线后同步分支代码 上线后合并远端开发分支到远端master&#xff1a; 本地分支提交到远端分支&#xff0c;git上远端分账合并请求到远端master 上线后合并远端master到远端开发分支&#xff1a; 同步远端master到本地master&#xff0c;将本地master合并到本地开发分…

DM8级联异步备库搭建及故障模拟将异步切换为实时同步

目录 一、 搭建前准备 二、 主库配置 2.1 dmini配置 2.2 dmmal.ini配置 2.3 dmarch.ini配置 2.4 dmwatcher.ini配置 2.5 dmtimer.ini配置 三、 异步备库配置 3.1 dm.ini配置 3.2 dmmal.ini配置 3.3 dmarch.ini配置 3.4 dmwatcher.ini配置 3.5 dmtimer.ini配置 四、…

【计算机网络】HTTP/HTTPS协议基础知识汇总

目录 1.URL&#xff1a; 2.HTTP协议&#xff1a; 2.1抓包工具&#xff08;这里用fiddler&#xff09;&#xff1a; 2.2请求和响应的格式&#xff1a; 2.3方法的介绍&#xff1a; 2.4请求报头&#xff08;header&#xff09;&#xff1a; 2.5状态码&#xff1a; 2.6响应…

格式化DataFrame中的时间数据DataFrame.to_datetime()方法

小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 格式化DataFrame中的时间数据 DataFrame.to_datetime()方法 选择题 关于以下python代码说法错误的一项是? import pandas as pd data {"Date": [2022/12/01,2022/12/02]} df pd…

SQL Server如何获取GUID号

select newid() guid;--获取GUID select replace(newid(),-,) guid;--获取GUID 去掉- sqlserver newid()函数 NEWID (Transact-SQL) - SQL Server | Microsoft LearnNEWID (Transact-SQL)https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql?redirect…

在大厂工作是这样的

应上面的一个小伙伴要求&#xff0c;让一个朋友整理了他做华为的工作经历&#xff0c;写的有些零散&#xff0c;希望对有大公司情怀的人所有帮助。35岁那年&#xff0c;拿到华为的社招offer。看着邮箱里面的录取通知&#xff0c;心里有高兴也有失落&#xff0c;难受的是看着身边…

腾讯云COS+PicGo+Typora十分钟搭建自己的图床

&#x1f468;‍&#x1f4bb;个人主页&#xff1a; 才疏学浅的木子 &#x1f647;‍♂️ 本人也在学习阶段如若发现问题&#xff0c;请告知非常感谢 &#x1f647;‍♂️ &#x1f4d2; 本文来自专栏&#xff1a; 常见软件安装与运用 ❤️ 支持我&#xff1a;&#x1f44d;点赞…

[附源码]java毕业设计逸尘房屋销售管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SSRF 漏洞笔记

什么是 SSRF 漏洞 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务端请求伪造&#xff09;是指攻击者向服务端发送包含恶意 URL 链接的请求&#xff0c;借由服务端去访问此 URL &#xff0c;以获取受保护网络内的资源的一种安全漏洞。SSRF 常被用于探测攻击者无…