Fork me on GitHub
余鸢

bean的范围

web-aware contexts中的其他范围

有几个范围仅在web-aware应用程序上下文中可用:

  • request - 每个HTTP请求创建新的bean实例
  • session - 每个HTTP会话创建新的bean实例
  • application - 每个ServletContext创建一个新的bean实例
  • globalSession - 在Portlet环境中为每个全局会话创建新的bean实例(在Servlet环境中,全局会话范围等于会话范围)
  • websocket - 根据WebSocket会话创建新的bean实例

在Spring Web MVC中声明和访问Web范围的bean不需要额外的设置。

XML配置

1
2
3
4
<bean id="myRequestBean" class="OneClass" scope="request"/>
<bean id="mySessionBean" class="AnotherClass" scope="session"/>
<bean id="myApplicationBean" class="YetAnotherClass" scope="application"/>
<bean id="myGlobalSessionBean" class="OneMoreClass" scope="globalSession"/>

Java配置(Spring 4.3之前)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class MyConfiguration {
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public OneClass myRequestBean() {
return new OneClass();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AnotherClass mySessionBean() {
return new AnotherClass();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public YetAnotherClass myApplicationBean() {
return new YetAnotherClass();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_GLOBAL_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public OneMoreClass myGlobalSessionBean() {
return new OneMoreClass();
}
}

Java配置(Spring 4.3之后)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class MyConfiguration {
@Bean
@RequestScope
public OneClass myRequestBean() {
return new OneClass();
}
@Bean
@SessionScope
public AnotherClass mySessionBean() {
return new AnotherClass();
}
@Bean
@ApplicationScope
public YetAnotherClass myApplicationBean() {
return new YetAnotherClass();
}
}

Annotation-Driven Components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
@RequestScope
public class OneClass {
...
}
@Component
@SessionScope
public class AnotherClass {
...
}
@Component
@ApplicationScope
public class YetAnotherClass {
...
}
@Component
@Scope(scopeName = WebApplicationContext.SCOPE_GLOBAL_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class OneMoreClass {
...
}
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AndOneMoreClass {
...
}

prototype-scope 原型范围

原型范围的bean不是在Spring容器启动时预先创建的。 相反,每次将检索此bean的请求发送到容器时,将创建一个新实例。 对于有状态对象,建议使用此作用域,因为其状态不会被其他组件共享。

为了定义一个原型范围的bean,我们需要添加@Scope注释,指定我们想要的范围类型。

给出以下MyBean类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyBean {
private static final Logger LOGGER = LoggerFactory.getLogger(MyBean.class);
private String property;
public MyBean(String property) {
this.property = property;
LOGGER.info("Initializing {} bean...", property);
}
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

我们定义一个bean定义,将其范围说明为原型:

1
2
3
4
5
6
7
8
9
@Configuration
public class PrototypeConfiguration {
@Bean
@Scope("prototype")
public MyBean prototypeBean() {
return new MyBean("prototype");
}
}

为了看看它是如何工作的,我们从Spring容器检索bean,并为其属性字段设置不同的值。 接下来,我们将再次从容器中检索bean并查找其值:

1
2
3
4
5
6
7
MyBean prototypeBean1 = context.getBean("prototypeBean", MyBean.class);
prototypeBean1.setProperty("changed property");
MyBean prototypeBean2 = context.getBean("prototypeBean", MyBean.class);
logger.info("Prototype bean 1 property: " + prototypeBean1.getProperty());
logger.info("Prototype bean 2 property: " + prototypeBean2.getProperty());

看看下面的结果,我们可以看到如何在每个bean请求上创建一个新的实例:

1
2
3
4
Initializing prototype bean...
Initializing prototype bean...
Prototype bean 1 property: changed property
Prototype bean 2 property: prototype

一个常见的错误就是假设每个调用或每个线程重建bean,但情况并非如此。 相反,创建一个实例PER INJECTION(或从上下文检索)。 如果一个Prototype作用域bean只被注入到一个单独的bean中,那么只有该Prototype作用域bean的一个实例。

Spring不管理原型bean的完整生命周期:容器实例化,配置,修饰和装配一个原型对象,将其传递给客户端,然后对原型实例没有进一步的了解。

singleton scope 单例范围

如果bean是使用singleton scope定义的,那么只有一个对象实例在Spring容器中初始化。 对此bean的所有请求都将返回相同的共享实例。 这是定义bean时的默认范围。

给出以下MyBean类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyBean {
private static final Logger LOGGER = LoggerFactory.getLogger(MyBean.class);
private String property;
public MyBean(String property) {
this.property = property;
LOGGER.info("Initializing {} bean...", property);
}
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

我们可以使用@Bean注释定义一个单例bean:

1
2
3
4
5
6
7
8
@Configuration
public class SingletonConfiguration {
@Bean
public MyBean singletonBean() {
return new MyBean("singleton");
}
}

以下示例从Spring上下文中检索同一个bean两次:

1
2
3
4
MyBean singletonBean1 = context.getBean("singletonBean", MyBean.class);
singletonBean1.setProperty("changed property");
MyBean singletonBean2 = context.getBean("singletonBean", MyBean.class);

当记录singletonBean2属性时,将显示消息“changed property”,因为我们刚刚检索了相同的共享实例。

由于实例在不同组件之间共享,因此建议为无状态对象定义单例范围。

Lazy singleton beans 延迟单例bean

默认情况下,singleton bean是预先实例化的。 因此,将在创建Spring容器时创建共享对象实例。 如果我们启动应用程序,将显示“Initializing singleton bean …”消息。

如果我们不希望bean被实例化,我们可以添加@Lazy注释到bean定义。 这将阻止在第一次请求之前创建bean。

1
2
3
4
5
@Bean
@Lazy
public MyBean lazySingletonBean() {
return new MyBean("lazy singleton");
}

现在,如果我们启动Spring容器,将不会出现“Initializing lazy singleton bean …”消息。 在第一次请求bean之前,不会创建该bean:

1
2
logger.info("Retrieving lazy singleton bean...");
context.getBean("lazySingletonBean");

如果我们在定义了单例和延迟单例bean的情况下运行应用程序,它将产生以下消息:

1
2
3
Initializing singleton bean...
Retrieving lazy singleton bean...
Initializing lazy singleton bean...