8. Bean 作用域 Scopes

124 阅读4分钟

8. Bean 作用域 Scopes

8.1 Spring Bean 作用域

类型说明
singleton默认 Spring Bean 作用域,一个 BeanFactory 有且仅有一个实例
prototype原型作用域,每次依赖查找和依赖注入生成新的 Bean 对象
request将 Spring Bean 存储在 ServletRequest 上下文中
session将 Spring Bean 存储在 HttpSession 中
application将 Spring Bean 存储在 ServletContext 中
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

	String SCOPE_SINGLETON = "singleton";

	String SCOPE_PROTOTYPE = "prototype";
@Component
@Scope("prototype")
public class Toy {
    
}

8.2 singleton Bean 作用域

SpringFramework 官方文档中有一张图,解释了单实例 Bean 的概念:

img

左边的几个定义的 Bean 同时引用了右边的同一个 accountDao ,对于这个 accountDao 就是单实例 Bean 。

1. 使用 singleton

SpringFramework 中默认所有的 Bean 都是单实例的,即:一个 IOC 容器中只有一个。下面咱演示一下单实例 Bean 的效果:

  1. 创建Bean+配置类

咱使用注解驱动式演示,先创建 ChildToy ,本案例演示中 Toy 不再是抽象类,直接定义为普通类即可。

public class Child {
    
    private Toy toy;
    
    public void setToy(Toy toy) {
        this.toy = toy;
    }
}
// Toy 中标注@Component注解
@Component
// @Scope("singleton")
public class Toy {
    
}

接下来创建配置类,同时注册两个 Child ,代表现在有两个小孩:

@Configuration
@ComponentScan("com.linkedbear.spring.bean.b_scope.bean")
public class BeanScopeConfiguration {
    
    @Bean
    public Child child1(Toy toy) {
        Child child = new Child();
        child.setToy(toy);
        return child;
    }
    
    @Bean
    public Child child2(Toy toy) {
        Child child = new Child();
        child.setToy(toy);
        return child;
    }
    
}
  1. 测试运行

编写启动类,驱动 IOC 容器,并获取其中的 Child ,打印里面的 Toy

public class BeanScopeAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanScopeConfiguration.class);
        ctx.getBeansOfType(Child.class).forEach((name, child) -> {
            System.out.println(name + " : " + child);
        });
    }
    
}

运行 main 方法,控制台中打印了两个 Child 持有同一个 Toy

child1 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@971d0d8}
child2 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@971d0d8}

说明默认情况下,Bean 的作用域是单实例的

8.3 prototype Bean 作用域

Spring 官方的定义是:**每次对原型 Bean 提出请求时,都会创建一个新的 Bean 实例。**这里面提到的 ”提出请求“ ,包括任何依赖查找、依赖注入的动作,都算做一次 ”提出请求“ 。由此咱也可以总结一点:如果连续 getBean() 两次,那就应该创建两个不同的 Bean 实例;向两个不同的 Bean 中注入两次,也应该注入两个不同的 Bean 实例。SpringFramework 的官方文档中也给出了一张解释原型 Bean 的图:

img

图中的 3 个 accountDao 是 3 个不同的对象,由此可以体现出原型 Bean 的意思。

其实对于原型这个概念,在设计模式中也是有对应的:原型模式。原型模式实质上是使用对象深克隆,乍看上去跟 SpringFramework 的原型 Bean 没什么区别,但咱仔细想,每一次生成的原型 Bean 本质上都还是一样的,只是可能带一些特殊的状态等等,这个可能理解起来比较抽象,可以跟下面的 request 域结合着理解。

1. 使用 prototype

下面咱也实际测试一下效果,体会原型 Bean 的使用。

  1. 修改Bean

Toy 的类上标注一个额外的注解:@Scope ,并声明为原型类型:

@Component
@Scope("prototype")
public class Toy {
    
}

注意,这个 prototype 不是随便写的常量,而是在 ConfigurableBeanFactory 中定义好的常量:

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

	String SCOPE_SINGLETON = "singleton";

	String SCOPE_PROTOTYPE = "prototype";

如果真的担心打错,建议引用该常量  ̄へ ̄ 。。。

  1. 测试运行

其他的代码都不需要改变,直接运行 main 方法,发现控制台打印的两个 Toy 确实不同:

child1 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@18a70f16}
child2 : Child{toy=com.linkedbear.spring.bean.b_scope.bean.Toy@62e136d3}

2. 原型Bean的创建时机

仔细思考一下,单实例 Bean 的创建咱已经知道,是在 ApplicationContext 被初始化时就已经创建好了,那这些原型 Bean 又是什么时候被创建的呢?其实也不难想出,它都是什么时候需要,什么时候创建。咱可以给 Toy 加一个无参构造方法,打印构造方法被打印了:

@Component
@Scope("prototype")
public class Toy {
    public Toy() {
        System.out.println("Toy constructor run ...");
    }
}

修改启动类,只让它扫描 bean 包,不加载配置类,这样就相当于只有一个 Toy 类被扫描进去了,Child 不会注册到 IOC 容器中。

    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.bean.b_scope.bean");
    }

重新运行 main 方法,发现控制台什么也没打印,因为没有 Toy 的使用需求嘛,它当然不会被创建。

下面这 3 个可有可无

8.4 request Bean 作用域

8.5 session Bean 作用域

8.6 application Bean 作用域

8.7 自定义 Bean 作用域

8.8 面试题