Spring-Bean的Scope
分析了Spring Scope的作用和实现类。并从源码分析了其Scoped Proxy作用和实现,并在最后用arthas进行了简单的验证。重点应该关注基于代理的其他作用域Bean注册的两个BeanDefinition异同点,以及为何会注册两个BeanDefinition
在 Spring 中,所有组件本质上都是 Bean,虽然它们的创建方式一致,但生命周期和缓存策略并不统一。为了支持不同的使用场景,Spring 设计了 Scope(作用域)机制,来决定 Bean 实例在容器中的缓存行为。
常见的 Scope
通用环境
- singleton:默认作用域。单例bean,由Spring进行全局缓存
- prototype:很多博客翻译为多例,其实容易误导,因为session和request中的bean也算多例。翻译为原型bean更好,因为它是基于 Bean 定义模板进行实例化 的原型模式,每次获取Bean时都会创建一个新的实例(即取即创建)。因此,它不能被缓存,也不能被循环依赖
web环境
session:web环境下的缓存在Session里的bean
- request:web环境下的缓存在Request里的bean。意味着每次新的Request,都需要创建新的bean
- application:web环境下缓存在ServletContext里的bean。是存在于另一个容器里单例bean
cloud环境
refresh:spring colud环境下的一种作用域,在这个作用域里的bean意味着每次环境刷新后(RefreshEvent事件触发),都需要创建新的bean,并destory以前bean。例如cloud环境下如果配置中心支持动态更改kv,每次修改kv后就出触发RefreshEvent事件
Scope实现
在 AbstractBeanFactory#doGetBean
方法中,所有非默认作用域(非 singleton/prototype)的 Bean 获取,均通过 Scope
接口实现支持。每种运行环境对应一个 Scope
实例,由它统一负责该环境下 Bean 的 创建、缓存与销毁。Spring提供的常见Scope实现如下
SessionScope:session域的实现
RequestScope:request域的实现
ServletContextScope:application域的实现
RefreshScope:refresh域的实现
SimpleThreadScope:用ThreadLocal实现的Bean的缓存域。即线程可见Bean
SimpleTransactionScope:事务作用域,将 Bean 缓存绑定到当前事务,内部存储在
TransactionSynchronizationManager.resources
中
session域
主要逻辑为获取当前request的HttpSession,从内部中获取bean,没有则再实例化bean并缓存到当前Session里
public class SessionScope extends AbstractRequestAttributesScope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) { // 加锁,避免多线程同时创建bean
return super.get(name, objectFactory);
}
}
// 父类中的AbstractRequestAttributesScope方法
public Object get(String name, ObjectFactory<?> objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
// 对于session域,是通过HttpSession#getAttribute方法操作的
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
// 创建bean
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
Object retrievedObject = attributes.getAttribute(name, getScope());
if (retrievedObject != null) {
scopedObject = retrievedObject;
}
}
return scopedObject;
}
}
@Scope注解
用于定义 Spring 容器中 Bean 的作用域(Scope)。该注解通常配合 @Component
或 @Bean
使用,控制 Bean 在容器中的生命周期及缓存策略
- value:指定当前 Bean 的作用域名称,可以使用我们自定义的scope。Spring解析时不会校验
- scopeName:同value
- proxyMode:指定当前 Bean 是否需要通过代理方式注入。枚举类型为
ScopedProxyMode
,常用值包括:DEFAULT
(默认值):等价于NO
NO
:不使用代理。意味着当前Bean不能被其他singleton bean给依赖注入INTERFACES
:JDK 动态代理,即接口代理TARGET_CLASS
:CGLIB 代理,即继承代理
Scope代理
还有个重要的问题,Spring 中非 singleton 作用域的 Bean 无法直接注入到 singleton Bean 中,原因在于singleton Bean一旦被创建,其内部的依赖也随之固定。而诸如 request
、session
等作用域下的 Bean 生命周期较短,其实例在运行时可能会动态变化。
所以为了解决这种作用域不一致带来的注入问题,Spring 提供了 作用域代理机制(Scoped Proxy)。核心思想为将实际的Scope Bean 包装成一个代理对象,注入到 singleton Bean 中;每次通过该代理访问时,都会根据当前上下文(如当前request或session)动态获取真实的目标 Bean。这样,从开发者视角来看,这个 Bean 就像是 singleton 一样稳定可用,但实际底层访问的却是随作用域动态变化的目标实例。
ScopedProxyUtils
是 Spring 在注册特定作用域(如 request
、session
)BeanDefinition 之前,用于创建作用域代理的工具类。它的核心作用是:在注册 BeanDefinition 前,将其包装成作用域代理,从而允许非 singleton Bean 被注入到 singleton Bean 中。其最终会在容器中注册一下两个 Bean
代理 Bean(Scoped Proxy)
- 类型为
ScopedProxyFactoryBean
,作用域为singleton
- Bean 名为原始 Bean 的名称(例如
userService
) - 负责在运行时根据当前作用域动态获取实际 Bean 实例
- 保留原始 Bean 的自动注入相关属性(如
@Primary
、限定符),因此它才是被注入到其他 Bean 中的“主 Bean”
目标 Bean(Target Bean)
- Bean 名为
"scopedTarget." + 原始名称
(如scopedTarget.userService
) - 保持原有的作用域定义(如
request
、session
等) - 被设置为不可自动注入(
autowireCandidate = false
)。都注入代理bean
- 类型为
核心源码
public abstract class ScopedProxyUtils {
// 代理目标 Bean 的命名前缀
private static final String TARGET_NAME_PREFIX = "scopedTarget.";
private static final int TARGET_NAME_PREFIX_LENGTH = TARGET_NAME_PREFIX.length();
/**
* 创建作用域代理:将一个具有非单例作用域的 Bean 包装为一个 ScopedProxyFactoryBean,
* 用于在注入到 singleton Bean 时仍保持作用域语义。
*
* 实现原理:将原始 Bean 注册为一个带前缀的新 Bean,然后为原始 Bean 名称注册一个代理 Bean,
* 每次访问这个代理 Bean 时,都会动态地从容器中获取当前作用域下的真实 Bean。
*/
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
// 原bean修改后的目标beanName: scopedTarget. + 原beanName
String targetBeanName = getTargetBeanName(originalBeanName);
// 创建 ScopedProxyFactoryBean 的定义,用于代理原始 Bean
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
// 设置代理bean中targetBeanName(用来在BeanFactory中原始bean)
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) { // cglib proxy
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
} else { // jdk proxy
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// 复制原始 Bean 的自动注入相关属性
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// 将原始 Bean 设为非primary且不可自动注入(避免与代理冲突,这样Spring注册的这个bean始终是代理bean)
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// 注册原bean到容器中,此时beanName已被替换
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// 返回代理 Bean 的定义(仍然使用原始Bean的beanName)
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
}
ScopedProxyFactoryBean
它构建了一个用于作用域代理的单例增强 Bean,这个bean具有如下特点
- **TargetSource 为
SimpleBeanTargetSource
**:每次方法调用时,动态从BeanFactory
获取目标 bean 实例(不做任何缓存) - 代理类实现了目标 bean 的所有接口和类:对外表现为原始 bean,可被其他 bean 正常依赖和注入(作为主 bean 使用)。
- 实现了
AopInfrastructureBean
接口:标记为基础设施类,避免再次被 AOP、事务、异步等机制增强。 - **仅使用一个拦截器:
DelegatingIntroductionInterceptor
**:- 对被“引入”的接口方法(如
ScopedObject#getTargetObject()
)进行特殊处理; - 对其他方法则调用
MethodInvocation#proceed()
,由原始 bean 执行具体逻辑。
- 对被“引入”的接口方法(如
核心源码
public class ScopedProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
// 将 BeanFactory 注入目标作用域对象(这样才有能力从BeanFactory中获取目标作用域对象)
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
// 设置代理目标为 scopedTargetSource(默认为SimpleBeanTargetSource,其获取目标对象每次都调用BeanFactory#getBean)
// 且scopedTargetSource的getTargetClass()方法会解析并返回原始bean的class,用于代理增强
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.");
}
// jdk代理判断
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));
// 设置该bean不会被其他自动代理 代理了
/*
* 为什么会这么设置?
* 这个bean以及是个代理增强bean了,它所需要增强的地方也是唯一的地方就是找到目标scope里真实的bean,并将方法的调用行为转移到目标作用域的真实的bean中去
*
* 例:session域的bean,该bean被自动注入的就应该是这个代理bean,这个bean存在的目的也只是找到session域
* 里对应的真实bean,然后调用该bean的方法全走session域的真实bean的方法,且由这个真实bean来进行其他自动代理的增强
*/
pf.addInterface(AopInfrastructureBean.class);
// 创建好代理对象
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
/**
* 单例
*/
public boolean isSingleton() {
return true;
}
}
arthas验证
测试类
package site.shanzhao.config;
@Component
@SessionScope
public class SessionScopeDemo {
}
class信息查询
用sc -d site.shanzhao.config.SessionScopeDemo命令,我这里只展示了增强的class,重点观察到
- 父类为site.shanzhao.config.SessionScopeDemo
- 相比普通AOP,多增加的接口有:
- org.springframework.aop.scope.ScopedObject
- org.springframework.aop.framework.AopInfrastructureBean
class-info site.shanzhao.config.SessionScopeDemo$$EnhancerBySpringCGLIB$$9851f0cb
code-source /Users/reef/IdeaProjects/soil/target/classes/
name site.shanzhao.config.SessionScopeDemo$$EnhancerBySpringCGLIB$$9851f0cb
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name SessionScopeDemo$$EnhancerBySpringCGLIB$$9851f0cb
modifier public
annotation
interfaces org.springframework.aop.scope.ScopedObject,java.io.Serializable,org.springframework.aop.framework.AopInfrastructureBean,org.springframework.aop.SpringProxy,org.springframework.aop.framework.
Advised,org.springframework.cglib.proxy.Factory
super-class +-site.shanzhao.config.SessionScopeDemo
+-java.lang.Object
class-loader +-jdk.internal.loader.ClassLoaders$AppClassLoader@311d617d
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@1b4ae4e0
classLoaderHash 311d617d
ognl查询beanName
# 使用ApplicationContext.getBeanNamesForType()方法,可知有两个SessionScopeDemo class的BeanDefinition
[arthas@35578]$ ognl "@site.shanzhao.util.ApplicationContextUtils@applicationContext.getBeanNamesForType(@site.shanzhao.config.SessionScopeDemo@class)"
@String[][
@String[scopedTarget.sessionScopeDemo],
@String[sessionScopeDemo],
]