在Spring Framework中,总共定义了6中bean的作用域,其中4种作用域只有当应用为web应用时才生效,并且Spring还支持自定义作用域。
Scope | Description |
---|---|
singleton | (默认作用域)在每个Spring IoC容器种,一个bean定义对应只会有唯一的一个bean实例 |
prototype | 一个bean定义可以有多个bean实例 |
request | 一个bean定义对应于单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个bean实例,且该实例仅在这个HTTP请求的生命周期里生效。该作用域仅仅适用于WebApplicationContext环境。 |
session | 一个bean定义对应于单个Session的生命周期。也就是说,每个HTTP Session都有一个bean实例,且该实例仅在这个HTTP Session的生命周期里有效。该作用域仅适用于WebApplicationContext环境。 |
application | 一个bean定义对应于单个ServletContext的生命周期。该作用域仅适用于WebApplicationContext环境。 |
wrbsocket | 一个bean定义对于单个webSocket的生命周期。该作用域仅适用于WebApplicationContext环境。 |
singleton
Singleton作用域指的是在整个Spring容器中,只会存在一个Bean实例。所有的请求都会共享这个唯一的实例。这意味着,无论在何时何地请求该Bean,都会返回同一个实例。Singleton作用域是Spring框架默认的作用域,也是最常用的作用域之一。具体来说,当容器启动时,Spring会根据配置文件或者注解扫描等方式创建并初始化Singlenton Bean实例,并将其放入一个单例池中。当应用程序需要使用该Bean时,Spring容器会从单例池中返回该Bean的实例,而不是创建一个新的实例。因此,所有的请求都会共享同一个实例,避免了重复创建和销毁Bean实例的开销,提高了应用程序的性能和效率。Singleton作用域的优点包括:
- 节省资源,提高性能:由于只有一个实例,避免了重复创建和销毁Bean实例的开销,可以节省系统资源,提高应用程序的性能和效率
- 全局唯一性:确保整个应用程序中只存在一个实例,避免了在多个实例之间共享数据时可能出现数据不一致的问题
- 可以在多个容器之间共享实例:由于Singleton Bean实例是在整个应用程序中唯一的,所以可以在多个Spring容器之间共享实例,方便应用程序的开发和部署
Singleton作用域也存在一些缺点:
- 线程安全问题:由于Singleton作用域的Bean实例是全局唯一的,所以在多线程环境下可能会出现线程安全问题
- 生命周期管理问题:由于Singleton的Bean实例是在Spring容器启动时创建的,而且只有在容器关闭时才会被销毁,所以无法手动控制Bean实例的创建和销毁
- 对象状态管理问题:如果Singleton作用域的Bean实例存在状态变化,可能会导致在不同请求之间产生互相干扰的情况,从而影响程序的正确性
代码示例
package com.powernode.Service;
import com.powernode.Dao.UserDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author:
* @Description: TODO
* @DateTime: 2024/9/19 15:19
*/
public class UserServiceImpl implements UserService{
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
public UserDao userDao;
public void setUserDao(UserDao userDao){
System.out.println("userDao的set方法执行了");
this.userDao = userDao;
}
@Override
public void add() {
logger.info("add():userDao:{}", userDao);
userDao.add();
}
@Override
public void update() {
logger.info("update():userDao:{}", userDao);
userDao.update();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
目前来说:scope属性有两个值
第一个值:singleton 单例(默认情况下就是单例的。)
第二个值:prototype 原型/多例
其实scope属性有多个值:
例如:request session
但是request session要求项目必须是一个web应用。
当你引入springmvc框架的时候,这两个值就可以使用了。
request:一次请求当中一个bean
session:一次会话中只有一个bean
-->
<bean id="userServiceBean" class="com.powernode.Service.UserServiceImpl" scope="singleton">
<property name="userDao" ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.Dao.UserDaoImpl" />
</beans>
@org.junit.Test
public void singletonTest(){
/*
* Spring默认情况下是如何管理这个Bean的:
* 默认情况下Bean是单例的。(单例:singleton)
* 在Spring上下文初始化的时候实例化。
* 每一次调用getBean()方法的时候,都返回那个单例的对象。
*/
ApplicationContext beans = new ClassPathXmlApplicationContext("spring-scope-bean.xml");
UserService userServiceBean1 = (UserService) beans.getBean("userServiceBean");
System.out.println(userServiceBean1);
UserService userServiceBean2 = (UserService) beans.getBean("userServiceBean");
System.out.println(userServiceBean2);
}
执行结果
在取消getBean方法之后,**setUserDao**的打印语句仍然执行了,说明单例模式下,Bean对象的创建是在初始化Spring上下文的时候就完成了。
取消getBean之后
prototype
Prototype作用域时Spring框架中的一种Bean作用域,它与Singleton作用域不同,每次请求该Bean时,都会** 创建一个新的实例**。也就是说,**每次Spring容器中获取Prototype Bean时,都会得到一个新的实例对象**。具体来说,当容器启动时,Spring会根据配置文件或注解扫描等方式创建并初始化Prototype Bean实例,但是不会将其放入单例池中,而是在每次请求该Bean时,都会重新创建一个新的实例,并返回给调用着。因此,每个调用者都会得到一个全新的Bean实例,彼此之间没有任何关联。Prototype作用域的优点:
- 灵活性:每次请求时都会创建一个新的实例,不同调用者得到的实例对象彼此之间没有任何关联,因此可以更灵活地控制Bean的创建和销毁
- 对象状态隔离:由于每个调用者都会得到一个全新的Bean实例,所以不同调用者之间不会互相干扰,从而避免了状态共享可能带来的问题
- 无线程安全问题:由于每个调用者都会得到一个全新的Bean实例,所以在多线程环境下不会出现线程安全问题
Prototype作用域的缺点包括:
- 性能开销:由于每次请求时都会创建一个新的实例,所以会消耗更多的系统资源和时间,可能会影响应用程序和性能
- 生命周期管理问题:由于Prototype作用域的Bean实例不是单例的,所以无法通过Spring容器来管理生命周期,程序员需要手动控制Bean实例的创建和销毁
- 对象共享问题:由于Prototype作用域的Bean实例不是单例的,如果一个Bean实例被多个对象引用,可能会出现对象共享问题
代码示例
```xml <?xml version="1.0" encoding="UTF-8"?><bean id="userServiceBean" class="com.powernode.Service.UserServiceImpl" scope="prototype">
<property name="userDao" ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.Dao.UserDaoImpl" />
```
@org.junit.Test
public void singletonTest(){
ApplicationContext beans = new ClassPathXmlApplicationContext("spring-scope-bean.xml");
UserService userServiceBean1 = (UserService) beans.getBean("userServiceBean");
System.out.println(userServiceBean1);
UserService userServiceBean2 = (UserService) beans.getBean("userServiceBean");
System.out.println(userServiceBean2);
}
执行结果
在多例模式下,取消getBean之后**setUserDao的打印语句并没有执行,所以可以推断出在初始化Spring上下文的时候,并没有创建Bean对象,而是在获取Bean对象的时候才创建。**
取消getBean之后
自定义scope
> 注意:自定义scope是不能够覆盖Singleton和Prototype作用域的 >自定义一个Scope,线程级别的Scope,在同一个线程中,获取的Bean都是同一个。跨线程则是不同的对象:(以下内容作为了解)
- 第一步:自定义Scope。(实现Scope接口)
- spring内置了线程范围的类:
org.springframework.context.support.SimpleThreadScope
,可以直接用。
- spring内置了线程范围的类:
- 第二步:将自定义的Scope注册到Spring容器中。
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myThread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
- 第三步:使用scope
<bean id="sb" class="com.rui.spring6.beans.SpringBean" scope="myThread" />
测试程序
@Test
public void testThreadScope() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb = ctx.getBean("sb", SpringBean.class);
System.out.println(sb);
SpringBean sb1 = ctx.getBean("sb", SpringBean.class);
System.out.println(sb1);
// 启动一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
SpringBean sb2 = ctx.getBean("sb", SpringBean.class);
System.out.println(sb2);
SpringBean sb3 = ctx.getBean("sb", SpringBean.class);
System.out.println(sb3);
}
}).start();
}