1. Bean的作用域
前引例子4
现在有一个公共的Bean对象,提供给A用户和B用户使用,然而在使用的时候A用户将Bean对象的数据进行修改,此时B得到的Bean对象是否是原来的Bean对象呢?
@Component
public class Users {
@Bean
public User user() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
@Controller
public class BeanScopeController {
@Resource
private User user;
public User getUser() {
User user1 = user;
//修改
user1.setId(2);
user1.setName("李四");
return user1;
}
}
@Controller
public class BeanScopeController2 {
@Resource
private User user;
public User getUser() {
User user2 = user;
return user2;
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//修改前的Bean对象
User user = context.getBean("user", User.class);
System.out.println("Bean对象修改前: " + user.toString() );
//A修改Bean对象后
BeanScopeController beanScopeController =
context.getBean("beanScopeController", BeanScopeController.class);
System.out.println("A对象修改后: " + beanScopeController.getUser().toString());
//B从Spring中获取Bean对象,看是修改前的值还是没有被修改
BeanScopeController2 beanScopeController2 =
context.getBean("beanScopeController2", BeanScopeController2.class);
System.out.println("B对象修改后: " + beanScopeController2.getUser().toString());
}
}
我们发现A对象修改Bean对象后,B对象从Spring中获取的Bean对象也跟着改变了。
why?
这是因为Spring的Bean对象默认情况下是单例状态(singleton),也就是所有人使用的都是同一个对象。Spring采用单例模式可以提高性能,所以Bean的默认作用域就是singleton单例模式。
什么是作用域
在之前的JavaSE时,一个临时变量出了大括号就销毁,意味着临时变量的作用域就是在这个大括号内。而Bean的作用域是指Bean在Spring框架中某种行为模式,比如上面的singleton单例作用域,就表示Bean在Spring中只有一份,他是全局共享的,当其他人修改了这个值之后,那么另外一个人读取到的就是修改后的值。
Bean的6种作用域
- singleton:单例作用域
- prototype: 原型作用域(多例作用域)
- request: 请求作用域
- session:会话作用域
- application: 全局作用域
- websocket: Http WebSocket作用域
在普通的Spring项目中只有前两种,后四种状态是Spring MVC中的值。
singleton
描述:咱们上面演示了,singleton单例作用域下的Bean在IOC容器中只存在一个实例,获取Bean和装配Bean都是同一个对象
场景:通常无状态的Bean使用单例作用域(无状态表示Bean对象的属性状态不需要更新)
prototype
描述:每次对该作用域下的Bean的请求都会创建新的实例,获取Bean以及装配Bean都是新的对象实例
场景:通常有状态的Bean使用该作用域,也就是Bean对象的属性状态需要更新的时候
request
描述:每次http请求会创建新的Bean实例,类似于prototype
场景: 一次http的请求和响应共享同一个Bean对象
session
描述: 在一个http session中,定义一个Bean对象
场景: 用户会话中共享一个Bean对象,例如记录一个用户的登入信息
application(了解)
描述: 在一个http servlet Context中,定义一个Bean实例
场景: Web应用的上下文信息,记录一个应用的共享信息
:::danger
singleton和application的区别
:::
- singleton是Spring core的作用域,而application是Spring web的作用域
- singleton作用域IOC容器,而application作用于Servlet容器
websocket(了解)
描述:在一个http webSocket的生命周期中,定义一个Bean对象
场景:WebSocket的每次会话中,保存一个Map结构的头信息,将用来包裹客户端消息头,第一次初始化后,直到WebSocket结束都是同一个Bean
设置作用域
使用@Scope标签声明Bean的作用域
@Component
public class Users {
// 设置为原型模式/多例模式
@Scope("prototype")
@Bean
public User user() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
修改为多例模式后,A对象的修改不影响B对象的使用
@Scope 标签即可以修饰方法也可以修饰类,@Scope 有两种设置方式:
- 直接设置值: @Scope(“prototype”)
- 使用全局变量设置: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
2. Spring的执行流程
- 启动Spring容器
- 实例化Bean(分配内存空间)
- 使用xml注册bean
- 配置bean的扫描路径
- 将Bean存储到spring中,通过类注解/方法注解进行装配
- 将bean从spring中读取出来,装配到相应的类中
3. Bean的生命周期
生命周期就是一个对象从创建到销毁的整个生命过程,我们把这个过程叫做一个对象的生命周期。
Bean的生命周期可以分为5个部分
- 实例化Bean:从无到有,将字节码转为内存中的对象,只分配了内存空间。这就类似于自己买了个毛坯房,里面是空的,只有空间
- 设置属性:类属性的加载,Bean的注入和装配。这就类似于装修之前购买装修材料
- 初始化:
- 各种通知,如使用BeanNameAware方法,类似于装修房子之前打电话叫各种师傅来施工
- 初始化的前置工作,类似于师傅到了现场,要勘察环境,制定装修方案
- 进行初始化工作,如使用@PostConstruct初始化(注解时代),使用init-method初始化(xml时代)。类似于有两类师傅,一类是凭科技干活,一类是凭经验干活的
- 初始化的后置工作,类似于装修之后的清理工作
- 使用Bean,类似于拎包入住
- 销毁Bean 如使用@PreDestroy, 类似于卖掉房子
:::danger
验证Bean的生命周期
:::
@Component
public class BeanLifeComponent implements BeanNameAware {
//通知
@Override
public void setBeanName(String s) {
System.out.println("执行了通知");
}
//进行初始化工作
@PostConstruct
public void postConstruct() {
System.out.println("执行了PostConstruct");
}
public void init() {
System.out.println("执行了init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("执行了销毁方法: preDestroy");
}
}
<?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="component"></content:component-scan>
<bean id="beanLifeComponent" class="component.BeanLifeComponent"
init-method="init"></bean>
</beans>
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
System.out.println("使用Bean");
bean.preDestroy();
}
验证了在初始化阶段,先执行通知,执行初始化工作时,先执行注解时代,在执行xml时代;再使用Bean,销毁bean对象。
:::info
验证设置属性是在初始化之前
:::
@Service
public class UserService {
public UserService() {
System.out.println("执行了UserService的构造方法");
}
public void sayHi() {
System.out.println("hello UserService");
}
}
@Controller
public class UserController {
@Resource
private UserService service;
@PostConstruct
public void postConstruct() {
service.sayHi();
System.out.println("执行了UserController的构造方法");
}
}
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = context.getBean("userController", UserController.class);
}
由于是先设置属性的,所以在UserController在Bean注入是比执行初始化方法@PostConstruct 早,因此先输出“执行了UserService的构造方法”。而且如果不是先设置属性,那么service就是null,就会报空指针异常。