spring之Bean的作用域--singleton & prototype

  首先,我们要理解什么叫Bean的作用域。我们都知道变量的作用域,即变量起作用的区域。类比可知,spring的Bean的作用域就是实例起作用的区域。

  spring的Bean的作用域包括单例(singleton)、原型(prototype)、request、session。

  singleton 被标注为singleton的类,只会被实例化一次,这个实例可以无限重复注入。

  prototype 被标注为prototype的类可以被实例化多次,每个实例只能注入一次,即每次注入prototype的实例时,要检查这个实例是否已经在其他地方注入过,如果已经注入过,则不能再使用这个实例,要新建一个实例。

  request,是一个请求周期内,实例可以重复注入。超过一个请求周期,再要注入就要新建实例,原来的实例不能使用了。

  session,是一个会话周期内,实例可以重复注入。超过一个会话周期,再要注入就要新建实例,原来的实例不能使用了。

  默认情况下,spring的Bean默认的作用域为单例,即在整个应用的周期内,每个类只创建一个实例。在大多数情况下,这种作用域是可以满足要求的。但是对于像电商网站的购物车这样的场景,单例作用域就不适用了,因为每个人的购物车都必须是独立的,不能使用同一个。

  写到这里的时候,我想起了自己做过的一个助学贷款的项目,当时我并没有关注过spring的Bean的作用域的问题,也就是说所有的学生使用的实例都是相同的,那为什么项目没有出现错误呢?毕竟每个的学生信息都是不同的,如果共享单例,必然会发生混乱。后来想通了,所有使用依赖注入的类都是service、dao,即这些实例都是单例的,那些存储信息的对象都是在方法中new出来的,哎,虽然事情早已过去,还是让我虚惊一场。

 

  我们先实验一下prototype类型,将prototype类型的Bird注入到UserController3中,代码如下:

 

package com.zaoren.bean;

import java.util.Date;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bird extends Animal {

    @Override
    public void move() {
        Date d = new Date();
        d.getDay();
        d.getDate();
        this.play();
    }
    
    @Override
    public void play() {
        
    }
}

 

 

package com.zaoren.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.zaoren.bean.Bird;

@RequestMapping("user3")
@Controller
public class UserController3 {
    
    @Autowired
    private Bird bird;

    @RequestMapping("test")
    public void test() {
        System.out.println("bird = "+bird);
    }
}

两次请求test方法,打印结果为:

打印结果竟然不符合我们的期望。明明将Bird类标注为prototype类型了,为什么两次请求注入的是同一个实例呢?

  这里我们要注意,spring处理请求时,首先要在容器中找到一个controller实例,用这个controller实例来处理请求。上面的代码中,类UserController3并没有标注为prototype类型,所以它默认为singleton类型,因此两次请求实际上是由同一个UserController3实例来处理的,同一个UserController3实例的Bird属性自然是相同的。

  这里可以看出,我们查找问题时,要使用联系和发散的思维方式。将局部问题放在整体过程中审视,并联系相关的知识和过往经验。

  由此可以看出,要想试验prototype的情况,需要将作用域同时设置在类Bird和类UserController3上,代码如下:

package com.zaoren.bean;

import java.util.Date;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bird extends Animal {

    @Override
    public void move() {
        Date d = new Date();
        d.getDay();
        d.getDate();
        this.play();
    }
    
    @Override
    public void play() {
        
    }
}

 

package com.zaoren.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.zaoren.bean.Bird;

@RequestMapping("user3")
@Controller
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserController3 {
    
    @Autowired
    private Bird bird;
    
    @RequestMapping("test")
    public void test() {
        System.out.println("bird = "+bird);
    }
}

 

请求test方法,控制台输出结果为:

这一次,在类UserController3上加了prototype作用域,所以两次请求调用的controller是不同的实例,两个UserController3实例都需要被注入一个Bird实例,由于类Bird的作用域为prototype,所以注入两个UserController3实例的Bird实例是不同的,即第二次请求创建UserController3实例时,又重新创建了一个Bird实例来注入,没有注入原来的Bird实例,控制台的输出结果符合我们的期望。

 

  这里,我又想到了另一个问题。我们的代码中,并没有将controller注入到任何地方,那为什么还可以对controller使用作用域呢?

  要知道,我们最初学习的时候,http请求都是由我们自己编写的servlet处理的,而spring是一个框架,它内部实际上封装了servlet,只不过servlet这个类并没有展示给我看。我们发起请求时,请求实际上先到达那个我们看不到的servlet,而servlet实例已经被注入了我们的controller实例,具体的业务处理正是由我们的controller实例来处理的。所以说controller的作用域也会起作用。

posted on 2019-11-19 16:57  星辰划过指尖  阅读(1643)  评论(0编辑  收藏  举报