Spring Bean 范围快速指南
1.概述
在这个快速教程中,我们将了解 Spring 框架中不同类型的 bean 作用域。 bean 的范围定义了该 bean 在我们使用它的上下文中的生命周期和可见性。 最新版本的 Spring 框架定义了 6 种作用域:
- singleton
- prototype
- request
- session
- application
- websocket
最后提到的四个范围,request、session、application和websocket,仅在 web 感知应用程序中可用。
2. 单例范围
当我们使用singleton范围定义 bean 时,容器会创建该 bean 的单个实例;对该 bean 名称的所有请求都将返回相同的对象,该对象被缓存。对对象的任何修改都将反映在对 bean 的所有引用中。如果未指定其他范围,则此范围是默认值。
让我们创建一个Person实体来举例说明作用域的概念:
public class Person {
private String name;
// standard constructor, getters and setters
}
之后,我们使用*@Scope注释定义具有singleton*范围的 bean:
@Bean
@Scope("singleton")
public Person personSingleton() {
return new Person();
}
我们还可以通过以下方式使用常量而不是String值:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
现在我们可以继续编写一个测试,表明引用同一个 bean 的两个对象将具有相同的值,即使它们中只有一个改变了它们的状态,因为它们都引用了同一个 bean 实例:
private static final String NAME = "Ann Emily";
@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文件应包含所用 bean 的 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="com.blogdemo.scopes.Person" scope="singleton"/>
</beans>
3. prototype范围
每次从容器请求时,具有prototype范围的 bean都会返回不同的实例。它是通过将值prototype设置为 bean 定义中的*@Scope*注解来定义的:
@Bean
@Scope("prototype")
public Person personPrototype() {
return new Person();
}
我们也可以像在singleton作用域中那样使用常量:
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
我们现在将编写一个与之前类似的测试,显示两个对象在 prototype范围内请求相同的 bean 名称。它们将具有不同的状态,因为它们不再引用同一个 bean 实例:
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 添加xml定义:
<bean id="personPrototype" class="com.blogdemo.scopes.Person" scope="prototype"/>
4. Web 感知范围
如前所述,有四个附加范围仅在 Web 感知应用程序上下文中可用。我们在实践中较少使用这些。
request范围为单个 HTTP 请求创建一个 bean 实例,而session范围为一个 HTTP 会话创建一个 bean 实例。
application范围为ServletContext的生命周期创建 bean 实例,而websocket范围为特定的WebSocket会话创建它。
让我们创建一个用于实例化 bean 的类:
public class HelloMessageGenerator {
private String message;
// standard getter and setter
}
4.1. Request范围
我们可以使用*@Scope注释定义具有request*范围的bean:
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
return new HelloMessageGenerator();
}
proxyMode属性是必要的,因为在 Web 应用程序上下文的实例化时刻,没有活动请求。Spring 创建一个代理作为依赖注入,并在请求中需要它时实例化目标 bean。 我们还可以使用*@RequestScope*组合注释作为上述定义的快捷方式:
@Bean
@RequestScope
public HelloMessageGenerator requestScopedBean() {
return new HelloMessageGenerator();
}
接下来,我们可以定义一个控制器,该控制器具有对requestScopedBean的注入引用。我们需要两次访问相同的请求以测试 Web 特定范围。
如果我们在每次请求运行时都显示该message,我们可以看到该值被重置为null,即使它后来在方法中被更改。这是因为每个请求都返回了不同的 bean 实例。
@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范围
我们可以用类似的方式定义具有session范围的 bean:
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator sessionScopedBean() {
return new HelloMessageGenerator();
}
还有一个专用的组合注解,我们可以使用它来简化 bean 定义:
@Bean
@SessionScope
public HelloMessageGenerator sessionScopedBean() {
return new HelloMessageGenerator();
}
接下来我们定义一个引用sessionScopedBean的控制器。同样,我们需要运行两个请求以显示message字段的值对于会话是相同的。
在这种情况下,当第一次发出请求时,值message为null。但是,一旦更改,该值将保留给后续请求,因为为整个会话返回相同的 bean 实例。
@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范围
application范围为ServletContext的生命周期创建 bean 实例。这类似于*singleton *范围,但在 bean 的范围方面有一个非常重要的区别。
当 bean 是application作用域时,bean 的同一个实例在同一个ServletContext中运行的多个基于 servlet 的应用程序之间共享,而singleton 作用域 bean 的作用域仅限于单个应用程序上下文。 让我们创建具有application范围的 bean :
@Bean
@Scope(
value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator applicationScopedBean() {
return new HelloMessageGenerator();
}
类似于*request 和session *范围,我们可以使用更短的版本:
@Bean
@ApplicationScope
public HelloMessageGenerator applicationScopedBean() {
return new HelloMessageGenerator();
}
现在让我们创建一个引用这个 bean 的控制器:
@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";
}
}
在这种情况下,一旦在applicationScopedBean中设置,值message将保留给所有后续请求、会话,甚至对于将访问此 bean 的不同 servlet 应用程序,只要它在相同的ServletContext中运行。
4.4. WebSocket 范围
最后,让我们使用websocket范围创建 bean :
@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator websocketScopedBean() {
return new HelloMessageGenerator();
}
首次访问时,WebSocket范围的 bean 存储在WebSocket会话属性中。每当在整个WebSocket会话期间访问该 bean 时,都会返回该 bean 的相同实例。
我们也可以说它表现出单例行为,但仅限于WebSocket会话。