在分析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)