1. 概述
本文快速讲解Spring框架中不同类型的Bean作用域(scope)。作用域定义了Bean的生命周期和可见性范围。最新版Spring框架定义了6种作用域:
- singleton
- prototype
- request
- session
- application
- websocket
后四种(request、session、application、websocket)仅适用于Web应用。
2. 单例作用域(Singleton Scope)
当Bean定义为单例作用域时,容器只会创建一个实例。所有对该Bean的请求都返回同一个被缓存的对象。任何对象修改都会在所有引用中生效。这是默认作用域。
创建Person实体类示例:
public class Person {
private String name;
// 标准构造器、getter和setter
}
使用@Scope
注解定义单例Bean:
@Bean
@Scope("singleton")
public Person personSingleton() {
return new Person();
}
也可以用常量替代字符串:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
测试代码验证单例特性:两个引用对象指向同一实例,修改其中一个会影响另一个:
private static final String NAME = "John Smith";
@Test
public void givenSingletonScope_whenSetName_thenEqualNames() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("scopes.xml");
Person personSingletonA = (Person) applicationContext.getBean("personSingleton");
Person personSingletonB = (Person) applicationContext.getBean("personSingleton");
personSingletonA.setName(NAME);
Assert.assertEquals(NAME, personSingletonB.getName());
((AbstractApplicationContext) applicationContext).close();
}
scopes.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personSingleton" class="org.baeldung.scopes.Person" scope="singleton"/>
</beans>
3. 原型作用域(Prototype Scope)
原型作用域每次请求都返回新实例。通过设置@Scope("prototype")
定义:
@Bean
@Scope("prototype")
public Person personPrototype() {
return new Person();
}
同样可用常量:
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
测试代码验证原型特性:两个请求返回独立实例,状态互不影响:
private static final String NAME = "John Smith";
private static final String NAME_OTHER = "Anna Jones";
@Test
public void givenPrototypeScope_whenSetNames_thenDifferentNames() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("scopes.xml");
Person personPrototypeA = (Person) applicationContext.getBean("personPrototype");
Person personPrototypeB = (Person) applicationContext.getBean("personPrototype");
personPrototypeA.setName(NAME);
personPrototypeB.setName(NAME_OTHER);
Assert.assertEquals(NAME, personPrototypeA.getName());
Assert.assertEquals(NAME_OTHER, personPrototypeB.getName());
((AbstractApplicationContext) applicationContext).close();
}
scopes.xml
添加原型Bean定义:
<bean id="personPrototype" class="org.baeldung.scopes.Person" scope="prototype"/>
4. Web相关作用域
四种Web专用作用域实际使用较少:
- request:单次HTTP请求创建实例
- session:HTTP会话创建实例
- application:ServletContext生命周期内创建实例
- websocket:WebSocket会话创建实例
创建测试类:
public class HelloMessageGenerator {
private String message;
// 标准getter和setter
}
4.1. 请求作用域(Request Scope)
定义请求作用域Bean:
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
return new HelloMessageGenerator();
}
proxyMode
是必要的,因为Web应用上下文初始化时无活跃请求。Spring创建代理注入依赖,实际请求时才创建目标Bean。
也可用快捷注解:
@Bean
@RequestScope
public HelloMessageGenerator requestScopedBean() {
return new HelloMessageGenerator();
}
控制器测试:每次请求返回新实例,状态重置:
@Controller
public class ScopesController {
@Resource(name = "requestScopedBean")
HelloMessageGenerator requestScopedBean;
@RequestMapping("/scopes/request")
public String getRequestScopeMessage(final Model model) {
model.addAttribute("previousMessage", requestScopedBean.getMessage());
requestScopedBean.setMessage("Good morning!");
model.addAttribute("currentMessage", requestScopedBean.getMessage());
return "scopesExample";
}
}
4.2. 会话作用域(Session Scope)
定义会话作用域Bean:
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator sessionScopedBean() {
return new HelloMessageGenerator();
}
快捷注解方式:
@Bean
@SessionScope
public HelloMessageGenerator sessionScopedBean() {
return new HelloMessageGenerator();
}
控制器测试:同一会话内保持状态:
@Controller
public class ScopesController {
@Resource(name = "sessionScopedBean")
HelloMessageGenerator sessionScopedBean;
@RequestMapping("/scopes/session")
public String getSessionScopeMessage(final Model model) {
model.addAttribute("previousMessage", sessionScopedBean.getMessage());
sessionScopedBean.setMessage("Good afternoon!");
model.addAttribute("currentMessage", sessionScopedBean.getMessage());
return "scopesExample";
}
}
4.3. 应用作用域(Application Scope)
应用作用域在ServletContext生命周期内创建实例。类似单例作用域,但关键区别在于:
- application作用域:同一ServletContext内的多个Servlet应用共享实例
- singleton作用域:仅限于单个应用上下文
定义应用作用域Bean:
@Bean
@Scope(
value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator applicationScopedBean() {
return new HelloMessageGenerator();
}
快捷注解方式:
@Bean
@ApplicationScope
public HelloMessageGenerator applicationScopedBean() {
return new HelloMessageGenerator();
}
控制器测试:所有请求/会话/应用共享状态:
@Controller
public class ScopesController {
@Resource(name = "applicationScopedBean")
HelloMessageGenerator applicationScopedBean;
@RequestMapping("/scopes/application")
public String getApplicationScopeMessage(final Model model) {
model.addAttribute("previousMessage", applicationScopedBean.getMessage());
applicationScopedBean.setMessage("Good afternoon!");
model.addAttribute("currentMessage", applicationScopedBean.getMessage());
return "scopesExample";
}
}
4.4. WebSocket作用域
定义WebSocket作用域Bean:
@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator websocketScopedBean() {
return new HelloMessageGenerator();
}
首次访问时,Bean存储在WebSocket会话属性中。整个WebSocket会话期间返回同一实例(类似单例,但仅限于当前会话)。
5. 总结
本文详细讲解了Spring提供的各种Bean作用域及其适用场景。单例和原型是基础作用域,而四种Web作用域则针对特定Web场景设计。选择正确的作用域能避免很多并发问题,踩坑时记得检查作用域配置。
完整示例代码可在GitHub项目中查看。