Spring注入之单例bean注入原型bean
文章目录 前言 一、结果分析 二、原理分析 三、流程分析 1、注册阶段 2、实例化阶段 3、调用阶段 总结前言我们知道在Spring当中bean的Scope类型包含有单例(singleton)和多例(prototype),后者又叫原型。当然,还有request类型、session类型等。其中单例类型是单个Spring容器只存在一个,而原型类型每次从容器中获取都会生成一个新的,因此从容器生命周期来看
前言
我们知道在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.spring.boot.example.entity;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.concurrent.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.spring.boot.example.entity;
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.spring.boot.example.entity.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名称为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, definitionHolder, this.registry);
而这个applyScopedProxyMode方法就会根据类上面的ScopedProxyMode
执行不同的策略,默认值为ScopedProxyMode.NO
。
static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}
从上面源码可以看出,只有当ScopedProxyMode
为TARGET_CLASS
才会进入到下面创建ScopedProxy
的逻辑,这也是为啥我们在第一节中在原型bean的scope注解中添加proxyMode = ScopedProxyMode.TARGET_CLASS
的原因,只有修改了proxyMode才会进入下面的逻辑。
public static BeanDefinitionHolder createScopedProxy(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) {
return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass);
}
而createScopedProxy的逻辑其实就是将原来要注册的原型bean改个名字注册,另外注册一个ScopedProxyFactoryBean
类型的bean并使用原来原型bean的名称。对应的源码如下
/**
* Generate a scoped proxy for the supplied target bean, registering the target
* bean with an internal name and setting 'targetBeanName' on the scoped proxy.
* @param definition the original bean definition
* @param registry the bean definition registry
* @param proxyTargetClass whether to create a target class proxy
* @return the scoped proxy definition
* @see #getTargetBeanName(String)
* @see #getOriginalBeanName(String)
*/
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// The target bean should be ignored in favor of the scoped proxy.
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
在这里将targetDefinition的AutowireCandidate和Primary属性设置为false,这样在自动注入的时候就不会考虑原型bean了,而是对应的FactoryBean.
2、实例化阶段
在容器启动过程中,会实例化单例bean,这个时候需要注入这个原型bean,其实会触发ScopedProxyFactoryBean
类型bean的初始化。我们看一下这个bean的定义。
其中ProxyConfig
用于为生成代理提供配置,AopInfrastructureBean
是一个标识接口,标识这个是Spring的AOP的一个基础结构,因此这个类不会被自动代理,即使被匹配上,也就是说这个类在Spring AOP框架中是不会再生成其他的代理的。另外实现了FactoryBean接口,这样这个bean的真实对象是通过getObject方法返回的,另外这个类实现了BeanFactoryAware接口,这样在这个bean初始化的时候会调用回调方法setBeanFactory
,而这个回调方法也是生成代理的核心,如下所示
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
"': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped proxy
// itself is not subject to auto-proxying! Only its target bean is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
这里不但生成了代理,而且设置了SimpleBeanTargetSource,这样后面代理才能获取到真实的目标对象。最后实例化的对象如下所示
注册到单例bean中的其实就是对象中的proxy属性对象。
3、调用阶段
调用单例bean中多例bean的方法的时候,会调用proxy代理对象的方法,这是一个CGLIB代理对象,所以会首先进入org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept
方法中,如下所示
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
其中org.springframework.aop.TargetSource#getTarget
就是获取原型bean的关键了。
总结
如果实在xml中配置的话
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="prototypeInstance" class="org.spring.boot.example.entity.PrototypeInstance" scope="prototype">
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
<bean id="sigletonInstance" class="org.spring.boot.example.entity.SigletonInstance">
<property name="prototypeInstance" ref="prototypeInstance"/>
</bean>
除了以上的方式解决单例bean注册原型bean的问题之外,还有另一种方式-方法注入,但是本质其实都是差不多的,都是通过CGLIB动态代理的方式,可以参考博客:Spring注入之方法注入
更多推荐
所有评论(0)