首先,我们要理解什么叫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的作用域也会起作用。