Spring注⼊之单例bean注⼊原型bean
⽂章⽬录
前⾔
我们知道在Spring当中bean的Scope类型包含有单例(singleton)和多例(prototype),后者⼜叫原型。当然,还有request类型、session 类型等。其中单例类型是单个Spring容器只存在⼀个,⽽原型类型每次从容器中获取都会⽣成⼀个新的,因此从容器⽣命周期来看,单例类型的bean可以认为是longer-lived scoped bean(长⽣命周期bean),⽽原型类型的bean可以认为是shorter-lived scoped bean(短⽣命周期bean)。由此可以引出⼀个问题:在单例bean当中注⼊原型bean之后,这个原型bean还是原型bean吗?很多⼈可能会问,这个问题有意义吗?平时根本碰不到。其实很多⼈对于单例bean和原型bean适⽤什么样的场景是分不清的,好像单例bean与原型bean的区别只不过⼀个是存储在单例缓存中,所以每次获取都是⼀样的,⽽原型bean不存在啥缓存,所以每次从容器获取都会重新创建⼀个。但这是从结果来分析的,还没说清楚单例bean和原型bean的适⽤场景。在官⽅⽂档中有如下这句话:
As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.
这句话可以说很好的总结了单例bean和原型bean的适⽤场景,前者适⽤有状态的,后者适⽤⽆状态的。啥是有状态的?啥是⽆状态的?⽐如常见的服务类,除了依赖的bean和⽅法之外没有其他记录状态的属性,假如在⼀个服务类中存在⼀个boolean类型的属性,还将其作为单例bean,这样就变为有状态的了,因为这个boolean类型属性随时可能被另⼀个使⽤者改变,此时为了隔离,就必须考虑每个使⽤者使⽤⼀个了,也就是要使⽤原型bean了。我们平时之所以很少遇到单例bean注⼊多例bean的问题的主要原因就是我们平时设计的控制层、业务层和数据层尽量屏蔽了有状态。所以上⾯的问题其实要回答的是,在单例bean当中注⼊⼀个原型bean之后,这个原型bean之中的状态是共享的吗?
⼀、结果分析
⾸先定义⼀个原型bean,在这个原型bean当中,有⼀个stateCount属性,每次调⽤print都会将这个属性加⼀并输出。对于原型bean,每次都获取⼀个新的,这个⽅法只会输出1.
package org.ity;
import t.annotation.Scope;
import org.springframework.stereotype.Component;
import urrent.atomic.AtomicInteger;
@Scope(scopeName ="prototype")
@Component
public class PrototypeInstance {
AtomicInteger stateCount =new AtomicInteger(0);
public void print(){
System.out.println(stateCount.incrementAndGet());
}
}
再定义⼀个单例bean,并注⼊上⾯的原型bean,在这个单例的printCode⽅法中调⽤原型bean的⽅法。
package org.ity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SigletonInstance {
@Autowired
private PrototypeInstance prototypeInstance;
public void setPrototypeInstance(PrototypeInstance prototypeInstance){ this.prototypeInstance = prototypeInstance;
}
public PrototypeInstance getPrototypeInstance(){
return prototypeInstance;
}
public void printCode(){
prototypeInstance.print();
}
}
从容器中获取这个单例bean,并多次调⽤其中的printCode。
从结果来看,这个状态变量其实是共享的,也就是说这个单例bean⾥⾯的这个原型bean其实只有⼀个。仔细想⼀下,在这个单例bean在容器中只会创建⼀次,也就是说注⼊原型bean也只有⼀次机会,所以单例bean当中的prototypeInstance属性只可能是⼀个。所以最后注⼊的原型bean其实变成了⼀个
单例bean了。那么这是我们想要的结果吗?当然不是,我们期待的结果是,每次调⽤prototypeInstance这个属性的⽅法,应该不会共享状态的。那么,可以办到吗?
像上⾯这样,我们在这个原型bean上⾯的Scope注解中添加属性值proxyMode = ScopedProxyMode.TARGET_CLASS,然后再次调⽤。
从结果来看,此时这个原型bean成了真正的原型bean,不再共享变量了。达到了我们的⽬标,那么,这⾥⾯的⽞机是什么呢?
⼆、原理分析
按照道理来说,SigletonInstance单例bean中注⼊PrototypeInstance原型bean只有⼀次机会,这个prototypeInstance属性只可能是⼀个对象,⽽不可能每次都不⼀样,所以这个对象肯定不能是⼀个真实的PrototypeInstance对象,⽽是⼀个代理对象,这样才能在每次调⽤其⽅法的时候有机会被改变。那么Spring中是怎么做到的呢?⾸先Spring容器中为这个原型bean保存了两个bean实例。⼀个名称
为prototypeInstance,另⼀个名称为scopedTarget.prototypeInstance,在前⾯的名称前⾯加了scopedTarget..如下图所⽰
仔细看下这两个bean的实际类型,前者为org.springframework.aop.scope.ScopedProxyFactoryBean,后者
为org.ity.PrototypeInstance,是不是很奇怪,其实Spring就是将真实的原型bean改了名字,⽽原来的名字对应了⼀
个FactoryBean。⽽获取这个FactoryBean的真实结果是通过getObject⽅法来获取的,如下所⽰
@Override
public Object getObject(){
if(this.proxy == null){
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
那么这个proxy属性是啥呢?其实这个proxy就是通过CGLIB⽣成的⼀个代理对象,在⽣成这个代理对象的时候会设置targetSource
为SimpleBeanTargetSource类型的对象。上⾯的proxy和这个targetSource都是上⾯FactoryBean实例的属性,在调⽤这个代理的时候会从targetSource中获取到真实对象,然后调⽤真实对象的属性,Spring只要在这个时候从beanFactory中获取原型bean(此时的bean名称
springboot aop为scopedTarget.prototypeInstance),那么每次⽅法调⽤都是不⼀样的。⽽Spring正是这么实现的。
这个SimpleBeanTargetSource中属性如上所⽰,⽽getTarget⽅法源码如下
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget()throws Exception {
return getBeanFactory().getBean(getTargetBeanName());
}
}
在每次调⽤代理的⽅法时候,会进⼊到org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept⽅法中,如下所⽰
不难看出,这⾥就是通过注⼊⼀个代理对象,在每次调⽤这个代理对象的时候,获取⼀个原型Bean,这样就相当于注⼊了⼀个原型bean。
三、流程分析
1、注册阶段
在Spring扫描注册bean的时候会进⼊到⽅法ClassPathBeanDefinitionScanner#doScan当中,这个⽅法当中有如下代码
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, istry);
⽽这个applyScopedProxyMode⽅法就会根据类上⾯的ScopedProxyMode执⾏不同的策略,默认值为ScopedProxyMode.NO。
static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry){
ScopedProxyMode scopedProxyMode = ScopedProxyMode();
if(scopedProxyMode.equals(ScopedProxyMode.NO)){
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
ateScopedProxy(definition, registry, proxyTargetClass);
}
从上⾯源码可以看出,只有当ScopedProxyMode为TARGET_CLASS才会进⼊到下⾯创建ScopedProxy的逻辑,这也是为啥我们在第⼀节中在原型bean的scope注解中添加proxyMode = ScopedProxyMode.TARGET_CLASS的原因,只有修改了proxyMode才会进⼊下⾯的逻辑。
public static BeanDefinitionHolder createScopedProxy(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry,boolean proxyTargetClass){
ateScopedProxy(definitionHolder, registry, proxyTargetClass);
}
⽽createScopedProxy的逻辑其实就是将原来要注册的原型bean改个名字注册,另外注册⼀个Scoped
ProxyFactoryBean类型的bean并使⽤原来原型bean的名称。对应的源码如下
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论