目录
一、Spring 的执行流程
二、Bean 的作用域
2.1 通过案例来观察 Spring 中Bean 对象默认的作用域
2.2 Bean 对象的 6 种作用域
2.3 给Bean 对象设置作用域
三、Bean 的生命周期
Bean 执行流程(Spring 执行流程):启动 Spring 容器 -> 读取Spring 配置文件-> 实例化 Bean 对象(分配内存空间,从无到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 注入到需要的类中(取操作)。
Spring 是一个包含众多工具方法的 IoC 容器,也可以认为 Spring 就是用来读取和存储 Bean 对象的,那么 Bean 对象就是Spring 中一个至关重要的角色。
设计一个公共的 Student 类的Bean对象,注册到 Spring 容器中,提供给 A用户 和 B用户使用,A用户不讲武德悄悄地修改 Student 类型Bean 对象的数据,B 用户在使用的 Bean 对象的时候发现数据已经被篡改了,相当的恼火~
Student 类如下:
@Data
public class Student {
private int id; // 学号
private String name; // 姓名
private int age; // 年龄
private String sex; // 性别
}
给大家介绍一个第三方的框架: lombok
"Lombok"是一个在Java开发中非常常用的开源框架。它的目标是通过自动化生成Java代码的样板代码来简化开发过程,减少冗余代码,并提高代码的可读性。
Lombok提供了一系列的注解,例如@Data、@Getter、@Setter、@NoArgsConstructor等,通过在类上添加这些注解,可以自动生成对应的getter、setter、构造函数等方法。这样可以大大简化Java类的编写,提高开发效率。
要使用Lombok,您需要在项目中添加Lombok的依赖,并在IDE中安装Lombok插件,以便在编译和运行时正确处理Lombok的注解。
Lombox 依赖:
org.projectlombok
lombok
1.18.24
provided
博主使用 @Data 注解就来源于 lombox ,@Data包含了 @Setter 和 @Getter 注解,也就是自动给Student 类创建 set 和 get 方法。
公共的Bean 对象,注入到 Spring 中:
@Component
public class Users {
//使用方法注解将 学生张三注入到 Spring 中
@Bean
public Student getStu() {
Student stu = new Student();
stu.setId(123456);
stu.setName("张三");
stu.setAge(18);
stu.setSex("男");
return stu;
}
}
用户A 从Spring 中获取 stu Bean 对象,并进行修改:
@Controller
public class UserA {
//从Spring 中获取Bean 对象
@Resource //@Autowired
private Student stu;
public Student getStu() {
System.out.println("用户A获取原Bean对象"+ '\n'+ stu.toString());
//对原Bean 的数据进行修改
stu.setName("李四");
stu.setSex("女");
return stu;
}
}
用户B 从Spring 中获取 stu Bean 对象,进行使用(打印):
@Controller
public class UserB {
//从Spring 中获取Bean 对象
@Resource //@Autowired
private Student stu;
public Student getStu() {
System.out.println("用户B获取被用户A修改过的 Bean :" + '\n' + stu.toString());
return stu;
}
}
启动类中获取用户A 和 用户B 对象,调用getStu() 方法,获取 stu Bean对象:
public class App {
public static void main(String[] args) {
//1. 获取Spring 上下文对象,里面管理着 bean 对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2. 获取指定的 Bean 对象
UserA userA = context.getBean("userA", UserA.class);
Student stuA = userA.getStu();
System.out.println("用户A:" + stuA.toString());
UserB userB = context.getBean("userB", UserB.class);
Student stuB = userB.getStu();
System.out.println("用户B:"+ stuB.toString());
}
}
案例分析:
以上操作的的执行结果的原因 Bean 默认情况下是单例模式(singleton:一个类只有一个实例),也就是说用户 A 和 用户 B 操作的都是同一个Bean 对象,所以用户A 修改了唯一的 Bean , 用户B 获取到的 Bean 就是被修改后的了。
在Spring 中Bean 的作用域默认是singleton 单例模式~
限定程序中变量的可用范围叫做作用域,Bean 对象的作用域指的是在 Spring 整个生命周期中的某种行为模式,例如:单例作用域(singleton)的意思就是在整个 Spring 中只有一份实例,且全局共享,当其他人修改了这个唯一的一份实例后,另一个人再获取到的实例,就是被修改后的值了。
Spring框架为Bean对象提供了多种作用域选项。以下是Spring框架中常见的6种Bean对象作用域:
Singleton(单例):在整个应用程序中只存在一个Bean实例。每次请求都将返回相同的实例。获取Bean(通过applicationContext.getBean等方法获取),装配Bean(即通过@Autowired,@Resource)
Prototype(原型):每次请求将创建一个新的Bean实例。每次请求都返回不同的实例。获取Bean(通过applicationContext.getBean等方法获取),装配Bean(即通过@Autowired,@Resource)
Request(请求):在每次HTTP请求中,都会创建一个新的Bean实例。每个HTTP请求都返回不同的实例。
Session(会话):在用户会话期间,都会创建一个新的Bean实例。对于同一用户的后续请求,将返回相同的实例。
Global Session(全局会话):在整个应用程序的全局会话期间,都会创建一个新的Bean实例。对于同一全局会话的后续请求,将返回相同的实例。
Application(应用程序):在整个Web应用程序的生命周期内,都会创建一个新的Bean实例。每个Web应用程序都返回相同的实例。
注意:作用域的使用取决于具体的应用场景和需求。Spring框架还支持自定义作用域,普通的Spring 项目只支持 singleton (单例),prototype (多例),剩下的4种状态是在 SpringMVC项目中可以设置。
使用 @Scope 标签就可以用来声明 Bean 的作用域, 如以下代码所示:
@Component
public class Users {
//声明为多例模式,每此请求都会实例一个新的Bean对象
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//使用方法注解将 学生张三注入到 Spring 中
@Bean(name = "stu") //给 Bean 对象起一个名字
public Student getStu() {
Student stu = new Student();
stu.setId(123456);
stu.setName("张三");
stu.setAge(18);
stu.setSex("男");
return stu;
}
}
@Scope 标签既可以修饰方法也可以修饰类,此处是修饰方法,@Scope 有两种设置方式:
1. 直接设置值: @Scope("prototype")
英语好的朋友可以直接把单词记住,也是蛮直接方便的昂~
2.使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
此时我们给上述例子Student 类型的Bean 对象设置了多例模式,这个时候运行一下看看:
所谓 Bean(对象) 生命周期指的就是一个普通对象从创建到销毁的整个过程,我们把整个过程称之为对象的生命周期。Bean 对象也就是一个普通的实例对象。
Bean 对象的生命周期分为以下五个部分:
1. 实例化: Bean (为 Bean对象分配内存空间(堆区))
2. 设置属性:在实例化后,容器会将配置的属性值或引用注入到Bean对象中,可以通过构造函数、setter方法或字段注入的方式完成。
3. Bean 对象的初始化 :Bean对象的初始化流程通常包括以下几个步骤:
加载Bean定义:首先,容器会读取配置文件或通过注解扫描等方式,获取Bean的定义信息,并将其存储在内存中。
创建Bean实例:根据Bean的定义信息,容器会实例化Bean对象。这可以通过调用默认构造函数或使用工厂方法等方式来完成。
注入依赖:一旦Bean实例创建完成,容器会检查Bean定义中所声明的依赖关系,并将相应的依赖注入到Bean实例中。依赖注入可以通过构造函数、setter方法或字段注入方式来实现。
实现Aware接口:如果Bean实现了特定的Aware接口(如BeanNameAware、BeanFactoryAware等),容器会调用相应的回调方法,以便提供一些额外的关于容器的信息。
Bean初始化前的处理:在Bean实例初始化之前,容器会调用BeanPostProcessor接口的实现类的postProcessBeforeInitialization()方法,允许开发者在初始化之前进行一些自定义逻辑的处理。
初始化Bean:实例化过程完成后,容器会根据Bean定义中的配置调用初始化方法。这可以是通过在Bean类中标注@PostConstruct注解或实现InitializingBean接口的afterPropertiesSet()方法来完成。
Bean初始化后的处理:在Bean实例初始化之后,容器会再次调用BeanPostProcessor接口的实现类的postProcessAfterInitialization()方法,允许开发者在初始化之后进行一些自定义逻辑的处理。
完成Bean的初始化:至此,Bean的初始化流程完成,可以将其添加到容器中,供其他Bean进行依赖注入或其他操作使用。
需要注意的是,以上是一个典型的Bean对象初始化流程,实际应用中可能会有一些差异,例如,使用了代理、继承了父类等情况会导致初始化流程有所不同。同时,不同的容器实现也可能会有一些细微的差异。
4. 使用 Bean : Bean 对象,就是一个普通的实例化的对象,使用都是一样的,看咋类是咋设计的
5. 销毁 Bean :
销毁Bean对象可以通过以下几种方式实现:
- 使用Destroy方法:可以在Bean类中实现DisposableBean接口,并重写其中的destroy()方法。在容器销毁Bean实例时,会自动调用该方法来销毁Bean对象。
public class MyBean implements DisposableBean { // ... @Override public void destroy() { // 销毁操作 // ... } }
- 使用@PreDestroy注解:可以在Bean类的销毁方法上添加@PreDestroy注解,当容器销毁Bean实例时,会触发该注解标记的方法进行销毁操作。
public class MyBean { // ... @PreDestroy public void destroy() { // 销毁操作 // ... } }
- 配置销毁方法:在配置文件(如XML配置文件)中,可以通过指定destroy-method属性来指定Bean销毁时调用的方法。该方法可以是Bean类中的任意公开方法。
需要注意的是,销毁Bean对象的时机由容器管理,通常发生在容器关闭时。如果是单例Bean,则在容器关闭或手动调用销毁方法时触发销毁操作。如果是原型或会话作用域(session scoped)的Bean,则需要手动销毁。
执行流程图 :
实例化和初始化的区别:
实例化和属性设置时 Java 级别的系统 “事件”,操作过程不可以人为干预,而初始化是给开发者提供的,可以在实例化之后,类加载完成之前进行自定义 “事件” 处理。
总的来说,实例化是创建Bean实例的过程,而初始化是对已创建的Bean对象进行属性赋值和回调方法调用的过程。实例化是创建Bean对象的基础步骤,而初始化则是在实例创建完成后对Bean对象进行配置和准备的重要步骤。
举个例子形象的描述Bean 对象的生命周期:买房子
1. 实例化:在某市盖了一套毛坯房(从无到有,将字节码转换为内存中的对象,只是分配了内存)
2. 设置属性:(容器会将配置的属性值或引用注入到Bean对象中)。购买装修材料,引入外部资源。
3. 初始化:给房子装修~
4. 使用 Bean 对象: 拎包入住~
5. 销毁 Bean 对象: 拆,或者卖掉这套房子~
一定是先设置属性,再进行初始化操作。
傍晚的月亮照着花开的田野