hexon
发布于 2025-12-17 / 5 阅读
0

6、扩展:ScopedProxyMode的作用和底层实现

在分析BeanDefinition扫描过程源码时,@ComponentScan注解中有一个ScopedProxyMode,本节我们来了解下它的作用和原理。

内容回顾

其实源码中有两个地方有这个ScopedProxyMode,一个处是在org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法中设置:

// 获取自定义的ScopedProxyMode
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
    scanner.setScopedProxyMode(scopedProxyMode);
}

另一处是在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法中处理:

// 如果设置了ScopedProxyMode,则会生成一个新的BeanDefinition,类型为ScopedProxyFactoryBean
definitionHolder =
    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);

这个方法才是重点

下面看org.springframework.context.annotation.AnnotationConfigUtils#applyScopedProxyMode方法:

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);
}

最终执行org.springframework.aop.scope.ScopedProxyUtils#createScopedProxy方法。

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 abd) {
			proxyDefinition.copyQualifiersFrom(abd);
		}

		// 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());
	}

在这个方法里面会创建一个新的RootBeanDefinition,并且类型是ScopedProxyFactoryBean,然后还设置了来源的BeanDefinition,最终返回了一个新的BeanDefinitionHolder。从变量名proxyDefinition就可以看出,这肯定是跟创建代理对象有关系了。

要掌握ScopedProxyMode的底层实现,需要对Spring中的依赖注入、FactoryBean、ProxyFactory有一定的理解。但这些内容目前我们还没有详细学习过,这里我们不做深入分析,先明白它可能会创建一个新的BeanDefinition然后注册。

具体使用

说明:因为依赖web环境,这个示例我是自己创建一个SpringBoot3的项目测试的。

先考虑一种场景,有两个Bean,一个是UserService,一个是RequestBean,代码如下

@Service
public class UserService {

    @Autowired
    RequestBean requestBean;

    public String test() {
        System.out.println(requestBean);
        return "success";
    }
}
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestBean {
}

从代码可以看出,UserService的作用域是单例,RequestBean的作用域是request,但是UserService中依赖注入了RequestBean。

了解过Bean的生命周期的都知道,单例Bean只会创建一次,然后缓存在单例池中,并且也只会做一次依赖注入,也就是说UserService只会创建一次,并且创建之后,requestBean属性对应的对象就固定住了。

那这就有问题了,因为RequestBean的作用域是request,需要每个请求对应不同的RequestBean,那对于上述代码该如何解决呢?

对于上述代码,如果直接启动Spring容器会报错,因为启动过程中会去创建UserService,从而会去进行依赖注入,但是依赖注入时发现RequestBean的作用域是request,但是容器启动过程中根本没有请求,请求都没有,就更加拿不到RequestBean来进行依赖注入了。

需要修改代码为

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
}

这就用到了ScopedProxyMode,表示告诉Spring容器,对于RequestBean,到时候创建的时候利用Cglib来创建一个代理对象。

如果实现了接口还可以指定为ScopedProxyMode.INTERFACES,走JDK动态代理

这样注入给UserService的就是一个RequestBean的代理对象了,而在Spring容器启动后,处理请求时,每次使用的都是RequestBean的代理对象,而通过这个代理对象就能根据不同的请求拿到不同的RequestBean对象了,从而解决了上述问题。

@Service
public class UserService {

    @Autowired
    RequestBean requestBean; // 容器启动时是RequestBean代理对象

    public String test() {
        System.out.println(requestBean);  // 但是实际使用的时候是RequestBean,这里相当于调用toString方法
        System.out.println(requestBean.getClass()); //  getClass() 是最终方法(final)无法被代理/拦截,所以必然返回真正对象的实际 Class,也就是代理类的 Class。
        return "success";
    }
}
多次调用接口测试输出结果:
com.tuling.entity.RequestBean@53a551dd
com.tuling.entity.RequestBean@5a99b1a3
com.tuling.entity.RequestBean@6f9a5c73

源码分析

在底层,在解析RequestBean生成BeanDefinition时,会解析@Scope注解,如果发现配置了proxyMode,则会生成一个新的BeanDefinition对象,最终注入到Spring容器中的就是这个新的BeanDefinition对象,并且传入对应的类为ScopedProxyFactoryBean。

ScopedProxyFactoryBean既实现了FactoryBean接口,也实现了BeanFactoryAware,因此在根据BeanDefinition对象创建Bean对象时,会先执行BeanFactoryAware的setBeanFactory(),而在这个方法中就会利用ProxyFactory创建一个RequestBean的代理对象,而FactoryBean的getObject()方法中就会获取此代理对象。

而生成代理对象的方法,就在getObject方法上面的org.springframework.aop.scope.ScopedProxyFactoryBean#setBeanFactory方法:

@Override
public void setBeanFactory(BeanFactory beanFactory) {
    if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
        throw new IllegalStateException("Not running in a 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());
}

在利用ProxyFactory生成代理对象的过程中,最关键的就是设置了一个TargetSource,为SimpleBeanTargetSource,它的源码实现为

@SuppressWarnings("serial")
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

	@Override
	public Object getTarget() throws Exception {
		return getBeanFactory().getBean(getTargetBeanName());  // 这里打断点,每次调用test接口都会进来,但这里计算一下getTargetBeanName(),会是scopedTarget.requestBean,也就是说requestBean这个名称其实是代理对象,而scopedTarget.requestBean才是被代理对象的名称
	}

}

这样就做到了,在RequestBean调用代理对象的某个方法时,会先调用getTarget()拿到具体的RequestBean对象,然后在执行具体的方法。

而在getBean()方法中,就会根据每个请求创建出不同的RequestBean对象了。


总结

通过本文学习,我们可能还有些细节不知道,比如getBean方法内部的实现,这些后面都会学习。但是经过这节的学习,至少看到下面的代码不会再感觉陌生的。

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)