Spring-Bean的作用域-Prototype
一、Prototype
非单一原型范围导致每次对特定 bean 发出请求时都会创建一个新 bean 实例。也就是说,bean 被注入到另一个 bean 中,或者通过getBean()
容器上的方法调用来请求它,都是返回一个全新的对象实例。
通常,应该对所有有状态 bean 使用原型作用域,对无状态 bean 使用单例作用域。
与其他作用域相比,Spring并不管理原型bean的完整生命周期。容器实例化、配置和组装一个原型对象,并将其传递给客户端,而不需要进一步缓存该原型实例。因此,尽管初始化生命周期回调方法在所有对象上都被调用,而不管作用域如何,但在原型的情况下,配置的销毁回调方法不会被调用。客户端代码必须清理原型作用域对象,并释放原型bean所持有的资源。要让Spring容器释放由原型作用域bean持有的资源,需要使用自定义bean后处理器,它保存了对需要清理的bean的引用。
看起来,似乎比单例模式要复杂,因为这种方式会不断创建实例,如果不进行清除,最终会导致资源耗尽
二、应用场景
- 代替new 实例化对象
- 资源不可共享,保证资源的安全性(某些情况下,需要每次获得处于某个状态的对象,但在每次使用后状态会发生变化)
三、基于XML
声明一个实体类
<bean id="prototypeEntity" class="com.example.PrototypeEntity" scope="prototype"/>
请求多个实例
<bean id="prototypeService" class="com.example.PrototypeService">
<property name="one" ref="prototypeEntity"/>
<property name="two" ref="prototypeEntity"/>
<property name="three" ref="prototypeEntity"/>
</bean>
这样子将获得三个不同的实例
这里需要注意的是:对于大局来说,每个请求获得的仍是一个实例,因为prototypeService是单例的
四、基于注解
创建一个实体类(或者其他有状态的对象)
public class PrototypeEntity {
private int flag = 0;
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
}
创建service类,这个类中有一个属性,以及一个方法,每次调用这个方法将会进行自增。并声明他的prototype作用域
@Service
public class PrototypeService {
@Autowired
private PrototypeEntity one;
@Autowired
private PrototypeEntity two;
@Autowired
private PrototypeEntity three;
public String getProto() {
one.setFlag(one.getFlag() + 1);
two.setFlag(two.getFlag() + 1);
three.setFlag(three.getFlag() + 1);
return "one:"+one.getFlag()+"\ntwo:"+two.getFlag()+"three:"+three.getFlag()+"\n";
}
/**
* 注册一个作用域为prototype的bean
* @return
*/
@Bean
@Scope("prototype")
public PrototypeEntity prototypeEntity(){
return new PrototypeEntity();
}
}
在Controller进行请求注入Service
@RestController
public class PrototypeController {
@Autowired
private PrototypeService prototypeService;
@RequestMapping("/getProto")
public String getProto(){
return prototypeService.getProto();
}
}
最后启动项目,在浏览器访问 http://ip:port/getProto ,看到浏览器返回的内容也许会有疑惑,“这不还是会在原有基础上自增吗?”
没错,这也就是前面所提到的在大局上来说,每个PrototypeEntity仍是单例的。(这涉及到单例注入原型的问题,以后会说明)
如果还察觉不出和Singleton的区别,你可以尝试把@Scope(“prototype”)去掉
/**
* 单例bean
*/
@Bean
public PrototypeEntity prototypeEntity(){
return new PrototypeEntity();
}
将会以+3的形式递增,因为每次请求的是同一个实例
可以将Controller和Server的作用域都声明为原型,这样每次整条链路都是新的实例,更多的应用可以自由发挥。
最后
当使用具有对原型 bean 的依赖的单例作用域 bean 时,请注意在实例化时解析依赖关系。因此,如果您将原型范围的 bean 依赖注入到单例范围的 bean 中时,则会实例化一个新的原型 bean,然后将依赖项注入到单例 bean 中。原型实例是唯一提供给单例作用域 bean 的实例。
但是,假设希望单例范围的 bean 在运行时重复获取原型范围的 bean 的新实例。不能将原型范围的 bean 依赖注入到您的单例 bean 中,因为该注入仅发生一次,当 Spring 容器实例化单例 bean 并解析并注入其依赖项时。(想在单例bean中获取原型的bean可以通过方法注入的方式去使得每次获取的原型bean是一个新的)