上篇文章介绍了Spring更简单的对象存储和取出,这篇文章我们将会介绍Bean 作用域和生命周期
1.作用域
1.1引例
首先这里的作用域和我们之前java学到变量的作用域可能有点不一样。我们之前学习的变量作用域指的是该变量的可使用范围,比如:
{
{
int a=0;
}//2
}//1
a变量只在括号2中生效。但是Bean的作用域指的是:
- Bean在Spring框架(项目)里面的某种行为模式
具体什么意思呢,我们先看例子:
有一个公共的Bean供A和B两个类使用,假设这个Bean里面有个属性为value=ikun,A第一次使用时拿到value=ikun,待B使用时他偷偷改成了value=只因,A下一次使用时就拿到了value=只因,但是预期value=ikun啊,从而导致了程序出现错误。
说好一起当ikun,你却偷偷开了团
用代码怎么演示呢?
@Controller
@Data
public class Ikun {
private String name="ikun";
}
@Controller
public class A {
@Autowired
private Ikun ikun;
public void show(){
System.out.println(ikun.getName());//我是ikun
}
}
@Controller
public class B {
@Autowired
private Ikun ikun;
public void show(){
ikun.setName("只因");//偷偷开团
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context=
new ClassPathXmlApplicationContext("spring-config.xml");
A a=context.getBean("a",A.class);
System.out.println("A第一次使用Ikun:");
a.show();
B b=context.getBean("b",B.class);
System.out.println("B偷偷开团,修改Ikun");
b.show();
System.out.println("A第二次使用Ikun:");
a.show();
}
}
注意:这里使用了@Data注解,属于lombok插件,可以帮我们处理set,get等等方法,喜欢可以去了解以下
运行得到结果:
首先怎么处理这样的问题呢?我们需要先知道原因。
原因是Bean默认的作用域是单例状态,也就是所有类用的都是同一个对象。可能会有人问,为什么默认是单例模式呢?因为对于单例模式(以前学过懒汉,饿汉等等),我们知道可以提高代码的性能。那怎么修改Bean的模式呢?修改之前,我们要先知道有哪些模式。
1.2Bean的六种作用域
- singleton:单例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
后四种都属于SpringMVC里面的值,普通的Spring只有前两种,本文只说前两种。
第一种已经说明,着重解释第二种
prototype:原型作⽤域(多例作⽤域)
这个与单例作用域恰恰相反,每一次装配(即@AutoWired)都是新的对象实例,也就是一个类修改Bean不会影响另一个类的Bean。
那怎么修改上面例子的Bean为该模式呢?
1.3设置作用域
使用@Scope 标签就可以⽤来声明 Bean 的作⽤域
为Ikun类添加注解
//或者 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope("prototype")
@Controller
@Data
public class Ikun {
private String name="ikun";
}
得到结果:
很明显,开团失败了
小结:
@Scope 标签既可以修饰⽅法也可以修饰类(上面就是修饰类)
@Scope 有两种设置⽅式:
- 直接设置值:@Scope(“prototype”)//适合记得住单词
- 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//idea会有提示,更方便
2.生命周期
解释生命周期之前,必须要先了解Spring的执行流程
2.1Spring 执行流程
这个我JavaEE第二课就介绍过,这里我直接给出流程:
Bean 执行流程(Spring 执行流程):启动 Spring 容器 -> 实例化 Bean(读取配置文件,分配内存空间,初始化) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)
这里可能会出一道面试题:
2.2Bean的生命周期
生命周期指的是有个对象从诞生到销毁的整个生命过程,Bean的生命周期注意分为以下五部分:
- 实例化Bean(为Bean分配内存空间)ex:拿到毛坯房
- 设置属性(Bean注入和装配)ex:购买装修材料
- 进行初始化工作
实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、
ApplicationContextAware 的接口方法;ex:通知装修工人
执行 BeanPostProcessor 初始化前置方法;ex:工人制定装修方案
执行 @PostConstruct 初始化方法,依赖注入操作之后被执行;
执行自己指定的 init-method 方法(如果有指定的话);ex:开始装修
执行 BeanPostProcessor 初始化后置方法;ex:处理装修后现场 - 使用Bean ex:住房
- 销毁Bean ex:拆迁,房子被击碎
个人而言,对于第二点设置属性(Bean注入和装配)如果有疑问的。看以下代码便知:
@Controller
public class B {
@Autowired //这一步就是设置属性
private Ikun ikun;
public void show(){
ikun.setName("只因");//偷偷开团
}
}
看到这里我们可以思考一个问题:
上面五个部分中,2和3能不能颠倒顺序?
显然是不能的,因为可能第三步中会用到第二步的设置好的属性Bean,如果颠倒就会导致出现空指针异常,因为Bean还没有注入和装配。
结束语
那么今天的第四课就到这里,下节课会进入SpringBoot的学习,喜欢的话还请多多为我投票吧(doge)