Spring-Bean的Scope


​ 分析了Spring Scope的作用和实现类。并从源码分析了其Scoped Proxy作用和实现,并在最后用arthas进行了简单的验证。重点应该关注基于代理的其他作用域Bean注册的两个BeanDefinition异同点,以及为何会注册两个BeanDefinition

​ 在 Spring 中,所有组件本质上都是 Bean,虽然它们的创建方式一致,但生命周期和缓存策略并不统一。为了支持不同的使用场景,Spring 设计了 Scope(作用域)机制,来决定 Bean 实例在容器中的缓存行为。

常见的 Scope

  • 通用环境

      1. singleton:默认作用域。单例bean,由Spring进行全局缓存
      2. prototype:很多博客翻译为多例,其实容易误导,因为session和request中的bean也算多例。翻译为原型bean更好,因为它是基于 Bean 定义模板进行实例化 的原型模式,每次获取Bean时都会创建一个新的实例(即取即创建)。因此,它不能被缓存,也不能被循环依赖
  • web环境

    • session:web环境下的缓存在Session里的bean

      1. request:web环境下的缓存在Request里的bean。意味着每次新的Request,都需要创建新的bean
      2. 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一旦被创建,其内部的依赖也随之固定。而诸如 requestsession 等作用域下的 Bean 生命周期较短,其实例在运行时可能会动态变化。

​ 所以为了解决这种作用域不一致带来的注入问题,Spring 提供了 作用域代理机制(Scoped Proxy)。核心思想为将实际的Scope Bean 包装成一个代理对象,注入到 singleton Bean 中;每次通过该代理访问时,都会根据当前上下文(如当前request或session)动态获取真实的目标 Bean。这样,从开发者视角来看,这个 Bean 就像是 singleton 一样稳定可用,但实际底层访问的却是随作用域动态变化的目标实例。

ScopedProxyUtils

​ 是 Spring 在注册特定作用域(如 requestsession)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
    • 保持原有的作用域定义(如 requestsession 等)
    • 被设置为不可自动注入(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],
]