【Spring】Bean的作用域(单例、多例、请求、会话、Application) - 实践
文章目录
概念
在 Spring Ioc&DI 戒断,我们学习了 Spring 是如何帮助我们管理对象的
- 通过
@Controller,@Service,@Repository,@Component,@Configuration,@Bean来声明Bean对象 - 通过
ApplicationContext或者BeanFactory来获取对象 - 通过
@Autowired,Setter方法或者构造方法等来为应用程序注入所依赖的Bean对象
我们来简单回顾一下
- 通过
@Bean声明bean,把bean存在Spring容器中
public class Dog
{
private String name;
public void setName(String name) {
this.name = name;
}
}
@Component
public class DogBeanConfig
{
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
}
- 从
Spring容器中获取Bean- 通过在代码中直接注入
ApplicationContext的方式
- 通过在代码中直接注入
@SpringBootTest
class ApplicationTests
{
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
DogBeanConfig dog1 = applicationContext.getBean(DogBeanConfig.class)
;
;
System.out.println(dog1);
}
}
司改代码,从 Spring 容器中多次获取 Bean
@SpringBootTest
class ApplicationTests
{
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
DogBeanConfig dog1 = applicationContext.getBean(DogBeanConfig.class)
;
;
System.out.println(dog1);
DogBeanConfig dog2 = applicationContext.getBean(DogBeanConfig.class)
;
;
System.out.println(dog2);
}
}
观察运行结果
发现输出的 bean 对象地址是一样的,说明每次从 Spring 容器中取出来的对象都是同一个
- 这也是“单例模式”
- 单例模式:确保一个类只有一个实例,多次创建也不会创建出多个实例
Bean 的作用域是值 Bean 在 Spring 框架中的某种行为模式
比如单例作用域:表示 Bean 在整个 Spring 中只有一份,它是全局共享的。那么当其他人修改了这个值之后,那么另一个人读到的就是被修改后的值
修改上述代码,给 UserController 添加属性 name
@SpringBootTest
class ApplicationTests
{
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
Dog dog1 = applicationContext.getBean(Dog.class)
;
dog1.setName("狗狗1");
System.out.println(dog1);
System.out.println(dog1.getName());
Dog dog2 = applicationContext.getBean(Dog.class)
;
;
System.out.println(dog2);
System.out.println(dog2.getName());
}
}
观察运行结果:
dog1和dog2为同一个对象,dog2拿到了dog1设置的值
那能不能将 bean 对象设置为非单例的(每次获取的 bean 都是一个新对象呢)
- 这就是
Bean的不同作用域了
Bean 的作用域
在 Spring 中支持 6 中作用域,后 4 种在 Spring MVC 环境才生效
singleton:单例作用域- 每个
Spring Ioc容器内同名称的Bean只有一个实例(单例)(默认)
- 每个
prototype:原型作用域(多例作用域)- 每次使用该
bean时会创建新的实例(非单例)
- 每次使用该
request:请求作用域- 每个
HTTP请求生命周期内,创建新的实例(Web环境中,了解)
- 每个
session:会话作用域- 每个
HTTP Session生命周期内,创建新的实例(Web环境中,了解)
- 每个
Application:全局作用域- 每个
ServletContext生命周期里内,创建新的实例(web环境中,了解)
- 每个
websocket:HTTP WebSocket作用域- 每个
WebSocket生命周期内,创建新的实例(web环境中,了解)
- 每个
我们来看简单的代码实现
定义几个不同作用域的 bean
@Component
public class DogBeanConfig
{
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototype() {
Dog dog = new Dog();
return dog;
}
@Bean
@RequestScope
public Dog requestDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@SessionScope
public Dog sessionDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@ApplicationScope
public Dog applicationDog() {
Dog dog = new Dog();
return dog;
}
}
@RequestScope等同于@Scope (value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)@SessionScope等同于@Scope (value =WebApplicationContext.SCOPE_SESSION, proxyMode =ScopedProxyMode.TARGET_CLASS)@ApplicationScope等同于@Scope (value =WebApplicationContext. SCOPE_APPLICATION, proxyMode =ScopedProxyMode. TARGET_CLASS)
proxyMode ⽤来为 springbean 设置代理 .proxyMode = ScopedProxyMode.TARGET_CLASS 表⽰这个 Bean 基于 CGLIB 实现动态代理,Request,session 和 application 作⽤域的 Bean 需要设置 proxyMode
代码测试
测试不同作用域的 bean 取到的对象是否一样
@RestController
public class DogController
{
@Autowired
private Dog singleDog;
@Autowired
private Dog prototypeDog;
@Autowired
private Dog requestDog;
@Autowired
private Dog sessionDog;
@Autowired
private Dog applicationDog;
@Autowired
private ApplicationContext applicationContext;
@RequestMapping("/single")
public String single(){
Dog contexDog = (Dog) applicationContext.getBean("singleDog");
return "dog: " + singleDog.toString() + ", contextDog: " + contexDog;
}
@RequestMapping("/prototype")
public String prototype() {
Dog contextDog = (Dog) applicationContext.getBean("prototypeDog");
return "dog: " + prototypeDog.toString() + ", contextDog: " + contextDog;
}
@RequestMapping("/request")
public String request(){
Dog contexDog = (Dog) applicationContext.getBean("requestDog");
return "dog: " + requestDog.toString() + ", contextDog: " + contexDog;
}
@RequestMapping("/session")
public String session() {
Dog contextDog = (Dog) applicationContext.getBean("sessionDog");
return "dog: " + sessionDog.toString() + ", contextDog: " + contextDog;
}
@RequestMapping("/application")
public String application() {
Dog contextDog = (Dog) applicationContext.getBean("applicationDog");
return "dog: " + applicationDog.toString() + ", contextDog: " + contextDog;
}
}
- 每个请求都获取两次
Bean @Autowired和applicationContext.getBean("singleDog")都是从Spring容器中获取对象
观察 Bean 的作用域
1. 单例作用域
地址: http://127.0.0.1:8080/single
多次访问,得到的都是同一个对象,并且 @Autowired 和 applicationContext.getBean() 也是同一个对象
2. 多例作用域
地址: http://127.0.0.1:8080/prototype
观察 ContextDog,每次获取的对象都不一样(注入的对象在 Spring 容器启动时,就已经注入了,所以多次请求访问也不会发生变化)
3. 请求作用域
地址: http://127.0.0.1:8080/request
在一次请求中,@Autowired 和 applicationContext.getBean() 也是同一个对象,但是每次请求,都会重新创建对象
4. 会话作用域
地址: http://127.0.0.1:8080/session
在一个 session 中,多次请求,获取到的对象都是同一个
换一个浏览器访问,发现会重新创建对象(另一个 session)
5. Application 作用域
地址: http://127.0.0.1:8080/application
在一个应用中,多次访问都是同一个对象
Application scope就是对于整个web容器来说,bean的作用域是ServletContext级别的- 这个和
singleton有点类似,区别在于:Application scope是ServletContext的单例,singleton是一个ApplicationContext的单例 - 在一个
web容器中ApplicationContext可以有多个(了解即可)
浙公网安备 33010602011771号