8. Bean 作用域 Scopes
8.1 Spring Bean 作用域
| 类型 | 说明 |
|---|---|
| singleton | 默认 Spring Bean 作用域,一个 BeanFactory 有且仅有一个实例 |
| prototype | 原型作用域,每次依赖查找和依赖注入生成新的 Bean 对象 |
| request | 将 Spring Bean 存储在 ServletRequest 上下文中 |
| session | 将 Spring Bean 存储在 HttpSession 中 |
| application | 将 Spring Bean 存储在 ServletContext 中 |
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
@Component
@Scope("prototype")
public class Toy {
}
8.2 singleton Bean 作用域
SpringFramework 官方文档中有一张图,解释了单实例 Bean 的概念:
左边的几个定义的 Bean 同时引用了右边的同一个 accountDao ,对于这个 accountDao 就是单实例 Bean 。
1. 使用 singleton
SpringFramework 中默认所有的 Bean 都是单实例的,即:一个 IOC 容器中只有一个。下面咱演示一下单实例 Bean 的效果:
- 创建Bean+配置类
咱使用注解驱动式演示,先创建 Child 和 Toy ,本案例演示中 Toy 不再是抽象类,直接定义为普通类即可。
public class Child {
private Toy toy;
public void setToy(Toy toy) {
this.toy = toy;
}
}
// Toy 中标注@Component注解
@Component
// @Scope("singleton")
public class Toy {
}
接下来创建配置类,同时注册两个 Child ,代表现在有两个小孩:
@Configuration
@ComponentScan("com.linkedbear.spring.bean.b_scope.bean")
public class BeanScopeConfiguration {
@Bean
public Child child1(Toy toy) {
Child child = new Child();
child.setToy(toy);
return child;
}
@Bean
public Child child2(Toy toy) {
Child child = new Child();
child.setToy(toy);
return child;
}
}
- 测试运行
编写启动类,驱动 IOC 容器,并获取其中的 Child ,打印里面的 Toy :
public class BeanScopeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanScopeConfiguration.class);
ctx.getBeansOfType(Child.class).forEach((name, child) -> {
System.out.println(name + " : " + child);
});
}
}
运行 main 方法,控制台中打印了两个 Child 持有同一个 Toy :
child1 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@971d0d8}
child2 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@971d0d8}
说明默认情况下,Bean 的作用域是单实例的。
8.3 prototype Bean 作用域
Spring 官方的定义是:**每次对原型 Bean 提出请求时,都会创建一个新的 Bean 实例。**这里面提到的 ”提出请求“ ,包括任何依赖查找、依赖注入的动作,都算做一次 ”提出请求“ 。由此咱也可以总结一点:如果连续 getBean() 两次,那就应该创建两个不同的 Bean 实例;向两个不同的 Bean 中注入两次,也应该注入两个不同的 Bean 实例。SpringFramework 的官方文档中也给出了一张解释原型 Bean 的图:
图中的 3 个 accountDao 是 3 个不同的对象,由此可以体现出原型 Bean 的意思。
其实对于原型这个概念,在设计模式中也是有对应的:原型模式。原型模式实质上是使用对象深克隆,乍看上去跟 SpringFramework 的原型 Bean 没什么区别,但咱仔细想,每一次生成的原型 Bean 本质上都还是一样的,只是可能带一些特殊的状态等等,这个可能理解起来比较抽象,可以跟下面的 request 域结合着理解。
1. 使用 prototype
下面咱也实际测试一下效果,体会原型 Bean 的使用。
- 修改Bean
给 Toy 的类上标注一个额外的注解:@Scope ,并声明为原型类型:
@Component
@Scope("prototype")
public class Toy {
}
注意,这个 prototype 不是随便写的常量,而是在 ConfigurableBeanFactory 中定义好的常量:
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
如果真的担心打错,建议引用该常量  ̄へ ̄ 。。。
- 测试运行
其他的代码都不需要改变,直接运行 main 方法,发现控制台打印的两个 Toy 确实不同:
child1 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@18a70f16}
child2 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@62e136d3}
2. 原型Bean的创建时机
仔细思考一下,单实例 Bean 的创建咱已经知道,是在 ApplicationContext 被初始化时就已经创建好了,那这些原型 Bean 又是什么时候被创建的呢?其实也不难想出,它都是什么时候需要,什么时候创建。咱可以给 Toy 加一个无参构造方法,打印构造方法被打印了:
@Component
@Scope("prototype")
public class Toy {
public Toy() {
System.out.println("Toy constructor run ...");
}
}
修改启动类,只让它扫描 bean 包,不加载配置类,这样就相当于只有一个 Toy 类被扫描进去了,Child 不会注册到 IOC 容器中。
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.bean.b_scope.bean");
}
重新运行 main 方法,发现控制台什么也没打印,因为没有 Toy 的使用需求嘛,它当然不会被创建。
下面这 3 个可有可无