目录
2. 创建一个 User 对象,然后将 User 对象 存储到 Spring 容器中
2.3 修改 User 对象中的属性,然后看结果(两个对象是否都被修改)
前言
上一篇文章已经介绍了如何更加简单的存储和获取 Bean 对象,那么 Bean 对象在 Spring 容器中的生命周期又是什么时期,是一直跟随 Spring 容器呢,还是可以随时创建和销毁的呢。所以这篇文章详细介绍一下 Bean 对象的作用域以及生命周期。
一、 Bean 的作用域
1. 安装Lombok插件
1.1 Lombok 简介
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。
1.2 Lombok 安装
(1)在pom.xml 配置文件中添加依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
(2)在开发工具中安装 Lombok 插件:
(3)Lombok 的使用:
2. 创建一个 User 对象,然后将 User 对象 存储到 Spring 容器中
2.1 创建User 对象
package com.java.demo.enity;
import com.sun.javafx.binding.StringFormatter;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class User {
private int id;
private String name;
}
2.2 将User 对象存储到 Spring 中
package com.java.demo.component;
import com.java.demo.enity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Bean
public User user(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
2.3 修改 User 对象中的属性,然后看结果(两个对象是否都被修改)
package com.java.demo.controller;
import com.java.demo.enity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private User user;
public void printUser() {
System.out.println(user);
//修改 User
User myUser = user;
myUser.setName("良月初十" + myUser);
System.out.println("myUser -> " + myUser);
System.out.println("user -> " + user);
}
}
2.4 获取 Bean 对象,看此对象是修改前的还是后的
package com.java.demo.controller;
import com.java.demo.enity.User;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Controller
public class UserController2 {
@Resource
private User user;
public void printUser2() {
System.out.println("user -> " + user);
}
}
import com.java.demo.controller.UserController;
import com.java.demo.controller.UserController2;
import com.java.demo.enity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");
UserController controller = context.getBean("userController",
UserController.class);
controller.printUser();
UserController2 controller2 = context.getBean("userController2",
UserController2.class);
controller2.printUser2();
}
}
2.5 运行结果:
2.6 结论:
其实这个现象和SE 阶段学习的浅拷贝的现象是一样的,就是一个引用指向了另一个引用所指向的对象,之后改变对象的值,两个引用所指向的对象都会发生改变。
我的预期是当一个 Bean 对象存储到了 Spring 容器中之后,一个程序猿拿到这个对象进行修改之后,当另一个程序猿再拿到这个对象还是原来的值,此时才是正常的情况。但是实际结果 这个 Bean 对象只有在整个 Spring 容器中只存储了一份,多个人进行修改的时候此时 这个对象也会随着一起改变。
所以 Bean 的作用域:默认是单例模式(在整个Spring容器中只有一份)
这也就意味着在任何一个地方修改 Bean 的值再去获取这个对象都是修改后的值。但是为啥又说默认是单例模式呢? 也就是 Bean 对象的作用域是可以进行设置的(不仅仅只有一种作用域)
3. Spring Bean 的6种作用域
3.1 作用域详解
Spring 容器在初始化一个 Bean 对象的时候,同时会指定该对象的作用域,如果不进行设置,默认就是单例模式。
1. singleton :单例模式 (spring 要保证性能,此时进行初始化就只有一份对象) 使用场景:一般用无状态的 Bean 适合使用该模式(无状态:Bean 对象的属性 在整个作用域中不需要被修改) |
2. prototype:原型模式(多例作用域)(每次获取 Bean 时,都会重新生成一个 Bean 对象,所以此时性能不高) 使用场景:一般有状态的 Bean 适合使用该模式 |
3. request:请求模式 (每次http请求都会创建一个 Bean 对象,类似于多例模式) 使用场景:一次HTTP的请求和响应共享的 Bean 对象 适用于 该模式。 |
4. session:会话模式 (在一个HTTP session 中,使用一个 Bean 对象)(如:我进入了学校官网的教务系统,在退出教务系统之前我的多次http请求都是共享一个 Bean 对象的) 使用场景:在一个用户会话中一般使用该种模式。 (也就是说session模式对 Bean 对象的共享程度 比 request 模式 的 程度高) |
5. application:全局模式 (一个 http servlet Context中会只使用一个 Bean 对象) 如: 如果只是一个context,此时就会使用一个 Bean 对象,如果有多个 context 对象,此时就是 一个 context 对象 对应一个 Bean 对象。 使用场景:Web级别的应用的上下文信息。(获取一个Spring 容器) |
6. websocket:HTTP WebSocket 模式 (每一个WebSocket的会话对应着一个 Bean 对象) 如:在网页版的电商软件的右下角的那个客服咨询就是一个 WebSocket会话(WebSocket 就是Socket的一个变种, 就是一个web 的长连接) 使用场景:只适用于 Spring WebSocket 项目。 |
注:前两种模式是 Spring 普通项目的模式,后四种模式适用于 Spring Web 项目中。
3.2 代码设置 Bean 的作用域
1. 直接设置值:@Scope("prototype") |
2. 使⽤常量设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) |
注:设置作用域的时期是在Bean 对象刚存储进来的时候就需要设置好了作用域,而不是在获取Bean 对象的时候才 设置作用域。
如下图所示:(直接设置值)
(使⽤常量设置)
二、Spring 的执行流程和 Bean 的生命周期
1. 启动容器 | 加载配置文件(类加载路径下的 spring-config.xml) |
2. 根据配置完成 Bean 对象的初始化 | 扫描 com.java.demo 包(包含底下所有的子包),然后看哪些包中的类有:五大 类注解 |
3. 将 Bean 对象存储到 Spring 容器中 | 如果有五大 类注解,就会将这个类实例化,存储到 Spring 容器中 |
4. 装配 Bean 的属性 | 如果 Bean 对象需要使用其他 Bean 对象作为属性,可以使用 @Autowired 和 @Resource 注解来实现对象的装配 |
1. 启动 spring 容器:
2. 根据配置完成 Bean 对象的初始化:
3. 将 Bean 对象存储到 Spring 容器中:
4. 装配 Bean 的属性:
3.2 Bean 的生命周期详解
生命周期:就是一个对象从有到无的一个整个的生命过程,称作一个对象的生命周期。
Bean 的生命周期大概分为5个部分:
1. 实例化 Bean 对象(给 bean 分配一块内存) |
2. 设置属性(Bean 对象的注入和装配) |
3. Bean 对象的初始化(这块下面进行详细介绍) |
4. 使用 Bean 对象 |
5. 销毁 Bean 对象 |
注:Bean 对象的实例化 和 初始化 不是一个概念,不可以混为一谈,实例化是分配内存空间的过程,初始化是通过执行自己的各种方法完成了构造一个完整的 Bean 对象的过程。
所以实例化和设置属性的时机是在初始化 Bean 对象之前。
(实例化就像是先买房,而初始化是装修,是需要先买房,再装修的)和JVM运行的流程是一样的关于JVM执行流程可以参考文章:JVM(Java虚拟机)详解 - 良月初十♧的博客
代码演示:
3.3 Bean 的初始化详解
- 各种通知: 实现各种 Aware 方法,例如:BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法
- 初始化前置方法:执行后 BeanPostProcessor
- 执行初始化方法: 1. @PostConstruct (依赖注入操作之后被执行)--> 注解方式 2. init-method 方法(如果设置了才会执行)--> init - method 方法
- 执行 BeanPostProcessor 初始化的后置方法
总结:Bean 对象生命周期(从有到无的过程):
1、 开辟内存空间 (注:实例化 ≠ 初始化) |
2、 设置属性 (上述两步都在实例化之前) |
3、 初始化 3.1 各种通知(初始化了一部分内容就开始通知一下) 3.2 初始化的前置方法 3.2 初始化方法 (两种实现方法:xml方式,注解方式) 3.3 初始化后置方法() |
4、 使用 和 销毁 Bean 对象 |
代码演示 Bean 对象的 初始化步骤:
package com.java.demo.component;
import org.springframework.beans.factory.BeanNameAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanComponent implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行了通知 BeanName -> " + s);
}
/*xml 初始化方法*/
public void myInit() {
System.out.println("XML 方式初始化 ");
}
@PostConstruct
public void doPostConstruct() {
System.out.println("注解的初始化方法");
}
public void sayHi() {
System.out.println("执行 sayHi()");
}
@PreDestroy
public void doPreDestroy() {
System.out.println("do PreDestroy");
}
}
//在启动类中
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");
BeanComponent component = context.getBean("beanComponent",
BeanComponent.class);
component.sayHi();
component.doPreDestroy();
运行结果: