1. 观察案例
User:
public class User {
public int id;
public String name;
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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
UserBean(公共的Bean):
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBean {
@Bean(name = "user1")
public User getUser(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
UserController1 在使用时,进行了修改操作:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController1 {
@Autowired
private User user1;
public User getUser1(){
User user = user1;
System.out.println("userController1 修改之前的 User: "+user);
user.setId(2);
user.setName("李四");
return user;
}
}
UserController2 再去使用公共 Bean:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController2 {
@Autowired
private User user1;
public User getUser2(){
User user = user1;
return user;
}
}
打印 UserController1 和 UserController2 公共 Bean 的值:
import beans.UserController1;
import beans.UserController2;
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");
UserController1 userController1 = context.getBean("userController1",UserController1.class);
User user1 = userController1.getUser1();
System.out.println("userController1 修改之后的 User: "+user1);
UserController2 userController2 = context.getBean("userController2",UserController2.class);
User user2 = userController2.getUser2();
System.out.println("userController2 在 userController1 修改之后读出的 User: "+user2);
}
}
结果:
原因:
Bean 作用域:bean 在 spring 整个框架中,默认所有人使用同一个对象,作用域默认是singleton(单例模式)
2. 作用域定义
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值
3. Bean 的 6 种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域.
singleton
:单例模式(默认)
prototype
: 原型模式(多例模式)
request
: 请求作用域(Spring MVC)
session
: 会话作用域(Spring MVC)
application
: 全局作用域(Spring MVC)
websocket
: HTTP WebSocket 作用域(Spring WebSocket)
注意:
后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种
3.1 singleton
描述:
该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象。
场景:
通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
备注:
Spring默认选择该作⽤域
3.2 prototype
描述:
每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的对象实例。
场景:
通常有状态的Bean 使⽤该作⽤域
3.3 request
描述:
每次http请求会创建新的Bean实例,类似于prototype
场景:
⼀次http的请求和响应的共享Bean
备注:
限定SpringMVC中使⽤
3.4 session
描述:
在⼀个http session中,定义⼀个Bean实例
场景:
⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
备注:
限定SpringMVC中使⽤
3.5 application
描述:
在⼀个http servlet Context中,定义⼀个Bean实例
场景:
Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
备注:
限定SpringMVC中使⽤
3.6 websocket
描述:
在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bea实例
场景:
WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直WebSocket结束都是同⼀个Bean。
备注:
限定Spring WebSocket中使⽤
单例作⽤域(singleton)和全局作⽤域(application)区别
1:
singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
2:
singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器
4. 设置 Bean 作用域
1:
使用枚举设置(类似于枚举,是常量的方式):@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
2:
直接设置值: @Scope(“prototype”)
4.1 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
4.2 @Scope(“prototype”)
5. Bean 生命周期
所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期.
Bean 的⽣命周期分为以下 5 ⼤部分:
1: 实例化(给 bean 分配内存空间)
2: 设置属性(对象注入)
3: 初始化
a)
执行各种通知(执行各种 Aware)
b)
执行初始化的前置方法(BeanPostProcessor)
c)
执行构造方法,两种执行方式,一种是执行 @PostConstruct,另一种实质性 init-method
d)
执行初始化的后置方法(BeanPostProcessor)
4: 使用 Bean
5: 销毁 Bean
(销毁容器的各种方法)
a)
@PreDestroy
b)
重写 DisposableBean 接口方法
c)
destroy-method
5.1 实例化和初始化的区别
实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理
5.2 生命流程"案例"
Bean 的⽣命流程看似繁琐,但咱们可以以⽣活中的场景来理解它,⽐如我们现在需要买⼀栋房⼦,那么我们的流程是这样的:
- 先买房(实例化,从⽆到有);
- 装修(设置属性);
- 买家电,如洗⾐机、冰箱、电视、空调等([各种]初始化);
- ⼊住(使⽤ Bean);
- 卖出去(Bean 销毁)。
5.3 演示代码
import org.springframework.beans.factory.BeanNameAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
//@Component
public class BeanLifeComponent implements BeanNameAware {
@PostConstruct
public void postConstruct() {
System.out.println("执行 @PostConstruct");
}
public void init() {
System.out.println("执行 init-method");
}
public void use() {
System.out.println("使用 bean");
}
@PreDestroy
public void preDestroy() {
System.out.println("执行了 @PreDestroy");
}
public void setBeanName(String s) {
System.out.println("执行了 Aware 通知");
}
}
xml 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="beans"></content:component-scan>
<bean id="beanLifeComponent" class="beans.BeanLifeComponent" init-method="init"></bean>
</beans>
测试:
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent beanLifeComponent = context.getBean("beanLifeComponent", BeanLifeComponent.class);
beanLifeComponent.use();
context.destroy();
}
}
结果: