从前面我们可以看出 Spring 是用来读取和存储 Bean,因此在 Spring 中 Bean 是最核心的操作资源
一.作用域
Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。比如 singleton 单例作用域,就表示 Bean 在整个 Spring 中只有一份,它是全局共享的,当有人修改了这个值之后,那么另一个人读取到的就是被修改后的值。
如我们在 Spring 中定义了一个单例的 Bean 对象 user(默认作用域为单例),具体实现代码如下:
@Component
public class UserBean {
@Bean
public User user() {
User user = new User();
user.setId(1);
user.setName("Java"); // 此行为重点:用户名称为 Java
return user;
}
}
然后,在 A 类中使用并修改了 user 对象,具体实现代码如下:
@Controller
public class AController {
@Autowired
private User user;
public User getUser() {
User user = user;
user.setName("MySQL"); // 此行为重点:将 user 名称修改了
return user;
}
}
最后,在 B 类中也使用了 user 对象,具体实现代码如下:
@Controller
public class BController {
@Autowired
private User user;
public User getUser() {
User user = user;
return user;
}
}
此时我们访问 B 对象中的 getUser 方法,就会发现此时的用户名为 A 类中修改的 “MySQL”,而非原来的 “Java”,这就说明 Bean 对象 user 默认就是单例的作用域。如果有任何地方修改了这个单例对象,那么其他类再调用就会得到一个修改后的值。
1.作用域分类
Bean 的 6 种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作用域。
- singleton:单例作用域
默认的作用域,只有一个全局对象。(通常无状态的 Bean 使用该作用域。无状态表示 Bean 对象的属性状态不需要更新。)
- prototype:原型作用域(多例作用域)
多例默认,每次访问都会创建一个新对象。(通常有状态的 Bean 使用该作用域。)
- request:请求作用域
只能在Spring MVC中使用,每次HTTP请求都会创建一个对象。
- session:会话作用域
只能在Spring MVC中使用,每次(session)会话使用一个bean对象
- application:全局作用域
只能在Spring MVC中使用,一个 http servlet context 中共享一个对象。
- websocket:HTTP WebSocket 作用域
只能在 WebSocket中使用
也可以说只有四种,前四种是常用的,后面两种不常用
设置作用域
使用 @Scope 标签就可以用来声明 Bean 的作用域
@Component
public class Users {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = "u1")
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java"); // 【重点:名称是 Java】
return user;
}
}
@Scope 标签既可以修饰方法也可以修饰类,@Scope 有两种设置方式:
- 直接设置值:@Scope(“prototype”)
- 使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
三. Spring 执行流程和 Bean 的生命周期
3.1 Spring 执行流程
Bean 执行流程(Spring 主要执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)。
3.2 Bean 生命周期
所谓的生命周期指的是⼀个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做⼀个对象的生命周期。
Bean 的生命周期分为以下 5 大部分:
-
实例化:为 Bean 分配内存空间;
-
设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
-
初始化:
- 执行各种通知;
- 执行初始化的前置方法;(BeanPostProcessor)
- 执行初始化方法;(@PostConstruct)
- 执行初始化的后置方法。(BeanPostProcessor)
- 使用 Bean:在程序中使用 Bean 对象;
- 销毁 Bean:将 Bean 对象进行销毁操作。
以上生命周期中,需要注意的是:“实例化”和“初始化” 是两个完全不同的过程,千万不要搞混,实例化只是给 Bean 分配了内存空间,而初始化则是将程序的执行权,从系统级别转换到用户级别,并开始执行用户添加的业务代码。
能不能先执行初始化再执行设置属性呢?也就是将生命周期中的步骤 2 和步骤 3 的执行顺序交换一下? 答案是否定的。想象一个场景,如果在初始化方法中要用到被注入对象的某个方法,比如以下代码:
@Controller
public class UserController {
@Resource
private UserService userService;
@PostConstruct // 初始化方法
public void postConstruct() {
userService.sayHi();
}
}
此时如果先执行步骤 2,先将 UserService 注入到当前类,再调用步骤 3 执行初始化,那么程序的执行是正常的。然而如果将交互步骤 2 和步骤 3 的执行顺序,那么程序执行就会报错(空指针异常)。