目录
一、 Bean 的作用域
1. 安装Lombok插件
1.1 Lombok 简介
1.2 Lombok 安装
2. 创建一个 User 对象,然后将 User 对象 存储到 Spring 容器中
2.1 创建User 对象
2.2 将User 对象存储到 Spring 中
2.3 修改 User 对象中的属性,然后看结果(两个对象是否都被修改)
2.4 获取 Bean 对象,看此对象是修改前的还是后的
2.5 运行结果:
2.6 结论:
3. Spring Bean 的6种作用域
3.1 作用域详解
3.2 代码设置 Bean 的作用域
二、Spring 的执行流程和 Bean 的生命周期
1. 启动 spring 容器:
2. 根据配置完成 Bean 对象的初始化:
3. 将 Bean 对象存储到 Spring 容器中:
4. 装配 Bean 的属性:
3.2 Bean 的生命周期详解
3.3 Bean 的初始化详解
前言
上一篇文章已经介绍了如何更加简单的存储和获取 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();
运行结果: