Spring 中 Bean 的作用域和生命周期
创始人
2024-02-14 07:35:17
0

目录

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;}@Overridepublic String toString() {return "Cat{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}
}

使用方法注解存储 Cat: 

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

张三的业务: 

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

李四的业务: 

@Controller
public class ScopeController2 {@Autowiredprivate 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")@Beanpublic 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)@Beanpublic 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 {@Overridepublic void setBeanName(String s) {System.out.println("执行各种通知:" + s);}/*** xml 中 init-method 指定的前置方法*/public void initMethod() {System.out.println("执行 init-method 前置方法");}/*** 改用注解后的前置方法*/@PostConstructpublic void PostConstruct() {System.out.println("执行 PostConstruct 前置方法");}/*** 销毁前执行方法*/@PreDestroypublic void PreDestroy() {System.out.println("执行 PreDestroy 销毁方法");}public void use() {System.out.println("使用 bean - 执行 use 方法");}
}

使用原始的 标签设置 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);// 使用 beancontroller.use();// 销毁 beancontext.destroy();}
}

执行结果: 

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

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

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

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

public class BeanLifeController implements BeanNameAware {// 依赖注入DI@Autowiredprivate UserService userService;@Overridepublic void setBeanName(String s) {System.out.println("执行各种通知:" + s);}// 初始化的前置方法@PostConstructpublic void PostConstruct() {// 在初始化的前置方法中调用userService.doUserService();System.out.println("执行 PostConstruct 前置方法");}
}

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


相关内容

热门资讯

阿里巴巴申请信息处理与展示方法... 国家知识产权局信息显示,阿里巴巴(深圳)技术有限公司申请一项名为“信息处理、展示方法、设备、存储介质...
迅雷起诉前CEO陈磊侵害公司利... 雷递网 乐天 1月15日 互联网反腐是企业发展中备受关注的话题,2026年第一起企业反腐案则是迅雷起...
未成年人犯罪率持续下降!饶平法... “这个活动很有用,我们知道了遇到危险时可以用法律保护自己,也明白不能做违法的事。”近日,饶平县人民法...
法援故事 | 劳动者维权遭遇拒... 引 言 杨先生因索要加班费遭公司拒绝, 与公司交涉困难重重, 让本就陷入困境的杨先生 内心十分无助。...
一律师先后9次在超市盗窃18瓶... 近期,浙江一家律师事务所的“90后”律师龚某某因犯盗窃罪被判处有期徒刑三年四个月,龚某某的律师执业证...
以案说法|求职遭遇“限男性” ... 人民日报记者 金歆 用人招聘可以要求“限男性”吗?如果政府部门对就业歧视怠于监管,谁能督促政府部门及...
法律条文巧变“生活指南” 贵州... 连日来,在贵州省多个权威媒体以及省律师协会官方视频号上,一则以“网络安全新规下,企业必须做的三件事”...
携程被反垄断调查:半年被两度约... 图片来源:网络 出品 | 搜狐科技 作者 | 张莹 编辑 | 杨锦 近日,市场监管总局根据前期核查,...
崇明地下供水管冻裂致民房受损,... 近日,崇明区一处老街地下供水管因低温天气引发破裂,紧邻的居民家中遭浸、财物受损,周边住户断水、交通受...
迅雷起诉前CEO陈磊侵害公司利... 开年的“大厂反腐第一案”已经拉开帷幕。 1月15日,澎湃新闻记者获悉,迅雷公司(Nasdaq:XNE...