Java Spring

Spring Bean的作用域

Spring Bean 的作用域即为对象的作用范围。Spring Bean的作用域由配置项 scope 来限定。

Scope配置项

作用域限定了 Spring Bean 的作用范围,在 Spring 配置文件定义 Bean 时,通过声明 scope 配置项,可以灵活定义 Bean 的作用范围。
想要容器创建的 bean 为单例模式,就设置 Scope 为 singleton;想要每次创建的 bean 都是不同的对象则使用 prototype

六大作用域

官方文档给出的bean作用域的简单介绍。image.gif文档的表格写着是六个作用域,但是很多时候都会被认为只有五个作用域。因为 applicationwebsocket 的作用差不多。所以会被认为是一个。

范围 描述
singleton (默认)将每个Spring IoC容器的单个 bean 定义范围限定为单个对象实例。
prototype 将单个 bean 定义的作用域限定为任意数量的对象实例。
request 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在可感知网络的 Spring 上下文中有效 ApplicationContext
session 将单个 bean 定义的范围限定为 HTTP 的生命周期 Session。仅在可感知网络的 Spring 上下文中有效 ApplicationContext
application 将单个 bean 定义的作用域限定为的生命周期 ServletContext。仅在可感知网络的Spring上下文中有效 ApplicationContext
websocket 将单个 bean 定义的作用域限定为的生命周期 WebSocket。仅在可感知网络的Spring上下文中有效 ApplicationContext

重点放在 singleton 和 prototype ,后面四个是对于WebMVC而言的,暂不进行详细阐述。

单例作用域

先看第一个单例范围,单例范围即无论使用多少个 Dao 去获取 bean ,每次获取到的都是一个相同的 bean对象。Spring Bean的作用域以及注解使用 - 图2「singleton」 与所说的单例模式基本是相同的,singleton 也是默认的作用域,如果没用指定 bean 对象的作用域则在整个上下文环境中通过 Spring IOC 获取到的 bean 都是同一个实例。
配置 bean 为 singleton 作用域代码:

  1. <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

接下来定义一个POJO类 — User来演示 scope 属性对实例的影响。

  1. public class User {
  2. private String name;
  3. private int age;
  4. public User() {
  5. }
  6. public User(String name, int age) {
  7. this.name = name;
  8. this.age = age;
  9. }
  10. /*toString和getter/setter*/
  11. @Override
  12. public String toString() {
  13. return "User{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getAge() {
  25. return age;
  26. }
  27. public void setAge(int age) {
  28. this.age = age;
  29. }
  30. }

再显示的把该 bean 的作用域设置为 singleton。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd ">
  7. <bean id="user" class="com.javastudyway.pojo.User" p:name="Hello" p:age="4" scope="singleton"/>
  8. </beans>

最后就是测试类了:

  1. import com.javastudyway.pojo.Student;
  2. import com.javastudyway.pojo.User;
  3. import org.junit.Test;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. public class MyTest {
  7. @Test
  8. public void testSingleton(){
  9. ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml");
  10. User user1 = context.getBean("user",User.class);
  11. User user2 = context.getBean("user",User.class);
  12. System.out.println(user1 == user2);
  13. }
  14. }

测试结果如下:从比较结果可以看出,程序两次从 IOC 容器中获取的 User 实例「地址比较结果相同」,说明 IOC 容器返回的是「同一个实例」Spring Bean的作用域以及注解使用 - 图3

原型作用域

与 singleton 刚好相反,prototype,是程序每次从 IOC 容器获取的 Bean 都是一个新的实例。这次把 bean 的配置修改成 「prototype」,再进行测试。

  1. <bean id="user" class="com.javastudyway.pojo.User" p:name="Java学习之道" p:age="4" scope="prototype"/>

在测试类中增加一个测试方法:

  1. @Test
  2. public void testPrototype(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml");
  4. User user1 = context.getBean("user", User.class);
  5. User user2 = context.getBean("user", User.class);
  6. System.out.println("两次对象地址比较结果为:" + (user1 == user2));
  7. System.out.println("user1 的hasCode为:" + user1.hashCode());
  8. System.out.println("user2 的hasCode为:" + user2.hashCode());
  9. }

测试结果为:Spring Bean的作用域以及注解使用 - 图4可以看到,程序两次从 IOC 容器中获取的 user1 和 user2 实例的 hashCode 不相同,说明 User 添加 prototype 作用域后,IOC 容器每次返回的都是一个新的实例。
对于作用域使用场景的总结:

有状态的bean应该使用prototype作用域; 对无状态的bean则应该使用singleton作用域。

使用注解声明bean的作用域

大多数时候都不想很麻烦的去修改 applicationContext.xml ,所以可以使用注解来配置 bean 的作用范围。具体步骤为

  • 使用 **@Component** 注解将 POJO 类声明为一个 bean;
  • 使用 **@Scope** 注解声明 bean 的作用域;
  • 修改 「applicationContext.xml」 开启扫描 POJO 包中的所有类的注解。

    修改POJO类

    在 POJO 类上增加 @Component,让 Spring 将 User 视为一个 bean。
    在 POJO 类上增加 @Scope 注解,标识该 bean 的作用域为 singleton。
    1. import org.springframework.context.annotation.Scope;
    2. import org.springframework.stereotype.Component;
    3. @Component("user")
    4. @Scope("singleton")
    5. public class User {
    6. private String name;
    7. private int age;
    8. public User() {
    9. }
    10. public User(String name, int age) {
    11. this.name = name;
    12. this.age = age;
    13. }
    14. @Override
    15. public String toString() {
    16. return "User{" +
    17. "name='" + name + '\'' +
    18. ", age=" + age +
    19. '}';
    20. }
    21. public String getName() {
    22. return name;
    23. }
    24. public void setName(String name) {
    25. this.name = name;
    26. }
    27. public int getAge() {
    28. return age;
    29. }
    30. public void setAge(int age) {
    31. this.age = age;
    32. }
    33. }

    开启注解扫描

    增加完注解后只需要对 applicationContext.xml 这个 Spring 的配置文件做上一小部分修改,即可实现注解开发了。
    开启 com.javastudyway.pojo 包中的所有类的注解扫描。
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:p="http://www.springframework.org/schema/p"
    6. xmlns:c="http://www.springframework.org/schema/c"
    7. xsi:schemaLocation="http://www.springframework.org/schema/beans
    8. https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    9. <!-- 开启注解扫描 -->
    10. <context:component-scan base-package="com.javastudyway.pojo"/>
    11. </beans>

    测试

    对 POJO 类加上注解和开启注解扫描后,就可以通过简单的注解来设置 bean 的作用域而不再需要去配置文件中慢慢修找修改了。用刚刚 测试原型作用域的方法 来测试用注解修改成 singleton 的 bean。
    Spring Bean的作用域以及注解使用 - 图5
    结果从原来的 「false,hashCode 不相等」变成了 「true,hashCode 相等」了。