文章目录
一、Bean 的作用域问题
1、案例
先定义一个 Cat 类:
public class Cat {
private int id;
private String name;
private int age;
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;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
定义一个公共的 Bean:
@Component
public class CatBeans {
@Bean
public Cat cat() {
Cat cat = new Cat();
cat.setId(1);
cat.setName("喵喵喵");
cat.setAge(8);
return cat;
}
}
小明 在使用时,修改了 Bean:
@Controller
public class ScopeController {
@Autowired
private Cat cat;
public void doScope() {
System.out.println("Do scope controller.");
System.out.println("原数据:" + cat.toString());
// 修改
Cat cat2 = new Cat();
cat2.setName("喵喵");
System.out.println("修改之后的数据: " + cat2.toString());
}
}
我们之后如果还要用这个 Bean:
@Controller
public class ScopeController2 {
@Resource
private Cat cat;
public void doSCope(){
System.out.println("Do scope controller 2.");
System.out.println(cat.toString());
}
}
测试:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
ScopeController scopeController =
context.getBean("scopeController", ScopeController.class);
scopeController.doScope();
System.out.println();
ScopeController2 scopeController2 =
context.getBean("scopeController2", ScopeController2.class);
scopeController2.doSCope();
System.out.println();
}
}
运行结果为:
2、分析
Spring 中 Bean 默认情况下是单例模式,即 cat 和 cat1 都指向了同一块区域,我们无论是修改 cat 还是 cat1 都会对这个 Bean 造成修改。
二、作用域定义
Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份。
1、Bean 的 6 种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,
最后四种是基于 Spring MVC ⽣效的(主要学习前四种 作用域):
- singleton:单例作⽤域(默认作用域),只有一个全局对象。
- prototype:原型作⽤域(多例作⽤域)每次访问都会创建一个新的对象。
- request:请求作⽤域(只能在 Spring MVC 中使用),每次 HTTP 请求都会创建一个对象。
- session:回话作⽤域(只能在 Spring MVC 中使用),每次(session)会话使用一个 bean 对象。
- application:全局作⽤域(只能在 Spring MVC 中使用),一个 http servlet context 中共享一个对象。
- websocket:HTTP WebSocket 作⽤域(只能在 Web Socket 中使用)
下面是官方的资料:
① singleton
- 官⽅说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
- 描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀ 个对象。
- 场景:通常无状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
- 备注:Spring默认选择该作⽤域
② prototype
- 官⽅说明:Scopes a single bean definition to any number of object instances.
- 描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的 对象实例。
- 场景:通常有状态的Bean使⽤该作⽤域
③ request
- 官⽅说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:每次http请求会创建新的Bean实例,类似于prototype
- 场景:⼀次http的请求和响应的共享Bean
- 备注:限定SpringMVC中使⽤
④ session
- 官⽅说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:在⼀个http session中,定义⼀个Bean实例
- 场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
- 备注:限定SpringMVC中使⽤
⑤ application
- 官⽅说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:在⼀个http servlet Context中,定义⼀个Bean实例
- 场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
- 备注:限定SpringMVC中使⽤
⑥ websocket
- 官⽅说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
- 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
- 备注:限定Spring WebSocket中使⽤
⑦ 单例作用域(singleton)和全局作用域(application)区别
- singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
- singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
2、设置作用域
我们可以使用 @Scope 标签来声明 Bean 的作用域,代码如下:
@Component
public class MyComponent {
// @Scope("prototype")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = "c1")
public void doComponent1() {
System.out.println("Do user component1.");
}
}
使用 @Scope 标签声明 Bean 作用域的两种方式:
- 直接输入作用域:@Scope(“prototype”)
- 采用枚举的方式:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注意:
- 在使用第一种方式的时候,要注意不能拼写错误;
- 使用第二种方式就不容易写错(idea 自带提示).
3、案例修改
通过刚刚的学习,我们知道了:可以用 @Scope 标签来声明 Bean 作用域。我们之前也分析过,出现问题的原因,即 Spring 中默认为 单例作用域(singleton),那我们只需要将公共的 Bean 的作用域修改为 多例作用域(prototype)即可,修改如下:
@Component
public class CatBeans {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public Cat cat() {
Cat cat = new Cat();
cat.setId(1);
cat.setName("喵喵喵");
cat.setAge(8);
return cat;
}
}
此时我们再运行程序,结果为:
三、Bean 原理分析
1、Bean 执行流程
Bean 执⾏流程(Spring 执⾏流程):
- 启动 Spring 容器
- 实例化 Bean(分配内存空间,从⽆到有)
- Bean 注册到 Spring 中(存操作)
- 将 Bean 装配到需要的类中(取操作)
2、Bean 生命周期
Bean 生命周期:
- 实例化(≠初始化):分配内存空间
- 设置属性:依赖注入 DI
- 初始化:
- 执行各种通知
- 初始化的前置方法:xml 文件里 定义的 init-method | @PostConstruct
- 初始化方法
- 初始化的后置方法
- 使用 Bean
- 销毁 Bean
① 实例化和初始化的区别
- 实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。
- 初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。
② 生命流程的“故事”
以买房为例:
- 买房(实例化)
- 装修(设置属性)
- 买家具(初始化)
- 入住(使用 Bean)
- 卖方(销毁 Bean)
③ 生命周期演示
//@Component
public class BeanLifeComponent implements BeanNameAware {
public void setBeanName(String s) {
System.out.println("执行了 Bean Name 通知:" +
s);
}
/**
* 方法名随意定义
* xml中 init-method 指定的方法
*/
public void initMethod(){
System.out.println("执行了 init-method 方法");
}
/**
* 方法名随意定义
*/
@PostConstruct
public void myPostConstruct(){
System.out.println("执行了 PostConstruct 方法");
}
/**
* 销毁前执行方法
*/
@PreDestroy
public void myPreDestroy(){
System.out.println("执行了 PreDestroy 方法");
}
public void run(){
System.out.println("执行了 run 方法");
}
}
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="com.wzr"></content:component-scan>
<bean id="beanlife" class="com.wzr.controller.BeanLifeComponent"
init-method="initMethod"></bean>
</beans>
调用类:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent component =
context.getBean("beanlife",BeanLifeComponent.class);
component.run();
context.destroy();
}
}
④ 思考:为什么要先设置属性在进行初始化呢?
这就是因为,我们初始化过程中,有很多的方法,这些方法可能会调用某个属性,如果没有设置属性,就会报错(空指向异常)