Spring进阶之Bean的自定义作用域(Scope)

前言

        在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。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值