前言
在spring中我们用的最多的是单例,除此之外还有多例(prototype),而且spring还提供了让开发人员自定义bean作用域的注解@Scope。先来看一下基本的用法。
@Scope
有这样一个需求,我们想自己控制的一个特定bean的作用域:首先自定义一个注解@MyScope,并用@Scope绑定域的名称:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("my")
public @interface MyScope {
//默认使用ciglib代理
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后写一个类实现scope接口:
public class MyScope implements Scope {
//bean的缓存
public static Object cache;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if(cache==null){
cache=objectFactory.getObject();
}
return cache;
}
@Override
public Object remove(String name) {
//清空缓存
Object r=cache;
cache=null;
return r;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
@Override
public String getConversationId() {
return null;
}
}
然后在定义一个component打上@MyScope注解:
@MyScope
@Component
public class TestBean {
public void justPirnt(){
System.out.println(this);
}
}
测试一下:
@Test
@SuppressWarnings("deprecation")
void testForScope() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestBean.class);
Scope myScope=new MyScope();
//注册自定义作用域
context.getBeanFactory().registerScope("my",myScope);
context.refresh();
TestBean bean=context.getBean(TestBean.class);
bean.justPirnt();
//remove,在下次获取bean会重新创建
myScope.remove("testBean");
bean=context.getBean(TestBean.class);
bean.justPirnt();
}
打印结果:
org.springframework.test.TestBean@712625fd
org.springframework.test.TestBean@606e4010
可以看到两次获取的bean不是同一个,在实际开发中,我们可以通过线程变量,监听器等手段控制bean的作用域。
源码分析
来看一下spring是如何实现的,在注册TestBean的BeanDefinition时,来到AnnotatedBeanDefinitionReader#doRegisterBean,这个方法之前介绍过:
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
//...
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
//ScopedProxyMode,是用来配置当前类的代理模式的,在我之前ioc的源码中有提到过
//主要用于scope非singleton的情况。因为非singleton的bean spring并不会立刻创建对象,这里的scope为我们自定义的scope
//如果需要注入一个自定义作用域bean时就产生一个代理对象,这时代理模式就起作用了。
//applyScopedProxyMode巧妙的偷天换日,把原来的BeanDefinition换成了ScopedProxyFactoryBean的BeanDefinition
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
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);
}
public static BeanDefinitionHolder createScopedProxy(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) {
return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass);
}
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
//先记一下原来的BeanDefinition相关信息
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
//创建ScopedProxyFactoryBean的BeanDefinition
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);
}
//复制原来bean的AutowireCandidate和Primary属性到proxyDefinition
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
//把原来的bean的AutowireCandidate和Primary都设置为false
//这样的话,在依赖注入时,会优先使用ScopedProxyFactoryBean而不是原对象
//因为容器中会有两个类型为TargetClass的BeanDefinition,一个时原有bean,一个是ScopedProxyFactoryBean
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
//注册原有的BeanDefinition
registry.registerBeanDefinition(targetBeanName, targetDefinition);
//把BeanDefinition换成proxyDefinition返回
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
把bean定义换成ScopedProxyFactoryBean后,容器在刷新时会创建名称为testBean的工厂bean ScopedProxyFactoryBean,并且它还实现了BeanFactoryAware,在注入BeanFactory的方法中会创建testBean的代理对象,一起看一下:
public class ScopedProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
//目标对象的对象源,里面的的getTarget方法每次都会调用容器的getBean方法获取
private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
@Nullable
private String targetBeanName;
@Nullable
//目标对象的代理对象
private Object proxy;
//...
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
this.scopedTargetSource.setTargetBeanName(targetBeanName);
}
@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()));
}
//这个拦截器DelegatingIntroductionInterceptor实现了aop的Introduction
//代表为一个类添加的新字段或方法定义,其实不太常用了,先不关注了
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
pf.addInterface(AopInfrastructureBean.class);
//创建代理对象
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
//...
@Override
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
}
可以看到ScopedProxyFactoryBean的getObject方法就是返回proxy代理对象。在调用context.getBean获取对象时,由于会ScopedProxyFactoryBean是一个FactoryBean,会调用getObject返回这个代理对象proxy,至于proxy的创建过程,和aop没什么区别,在之前aop的博文已经详细介绍了。
我们主要看一下到底是如何获取原目标bean的,又是如何在合适的时候让bean刷新的。让我们调用bean.justPirnt(),这时的bean就是代理对象proxy,来到ciglib的代理类CglibAopProxy的拦截器DynamicAdvisedInterceptor#intercept:
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;
}
//从targetSource获取原对象,这里对于scope注解的对象很关键
//scope注解的对象在这里的targetSource为SimpleBeanTargetSource
//它的getTarget会调用getBean从bean工厂创建bean
//而scope注解的类在创建时,会调用我们自定义的scope对象的get方法
//我们可以在scope对象里实现我们自己想要的bean的作用域
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
}
else {
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) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
我们只看关键的方法,至于其他设计aop的逻辑在之前aop相关博文已经详细介绍。
target = targetSource.getTarget();
这行代码是最关键的,这时的targetSource就是ScopedProxyFactoryBean中的SimpleBeanTargetSource:
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
return getBeanFactory().getBean(getTargetBeanName());
}
}
可以看到它的getTarget方法会重新从容器中获取原目标bean,这是就又来到了getBean的逻辑,还记得之前ioc的源码有一段判断bean的作用域吗,在doGetBean中对于自定义的作用域会来到下面的if逻辑中:
//自定义作用域
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
//从注册过的自定义作用域中获取对应的scope对象
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//使用scope对象的get方法获取目标bean对象
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
我们之前写的MyScope类的get方法:
public Object get(String name, ObjectFactory<?> objectFactory) {
if(cache==null){
cache=objectFactory.getObject();
}
return cache;
}
先从缓存取,如果没有那么使用传进来的对象工厂也就是createBean重新创建bean。当我们想要刷新bean时,只要执行MyScope的remove方法清空缓存,那么下一次获取bean时就会重新创建啦。
总结
springMVC,springCloud都扩展了scope,如requestScope,bean的作用域为一次请求;再如我们很常用的@RefreshScope动态刷新配置,底层原理也是scope。