Spring Scope简析

Spring中提供了一个如下的注解:

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;

/**
 * When used as a type-level annotation in conjunction with
 * {@link org.springframework.stereotype.Component @Component},
 * {@code @Scope} indicates the name of a scope to use for instances of
 * the annotated type.
 *
 * <p>When used as a method-level annotation in conjunction with
 * {@link Bean @Bean}, {@code @Scope} indicates the name of a scope to use
 * for the instance returned from the method.
 *
 * <p><b>NOTE:</b> {@code @Scope} annotations are only introspected on the
 * concrete bean class (for annotated components) or the factory method
 * (for {@code @Bean} methods). In contrast to XML bean definitions,
 * there is no notion of bean definition inheritance, and inheritance
 * hierarchies at the class level are irrelevant for metadata purposes.
 *
 * <p>In this context, <em>scope</em> means the lifecycle of an instance,
 * such as {@code singleton}, {@code prototype}, and so forth. Scopes
 * provided out of the box in Spring may be referred to using the
 * {@code SCOPE_*} constants available in the {@link ConfigurableBeanFactory}
 * and {@code WebApplicationContext} interfaces.
 *
 * <p>To register additional custom scopes, see
 * {@link org.springframework.beans.factory.config.CustomScopeConfigurer
 * CustomScopeConfigurer}.
 *
 * @author Mark Fisher
 * @author Chris Beams
 * @author Sam Brannen
 * @since 2.5
 * @see org.springframework.stereotype.Component
 * @see org.springframework.context.annotation.Bean
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

该注解和@Component注解一起使用,用于定义一个Bean的作用域是singleton还是prototype,当然在web中,还有session和request。

ScopeDescription
singleton(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototypeScopes a single bean definition to any number of object instances.
requestScopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
sessionScopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
applicationScopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocketScopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

此处仅讨论一下singleton还是prototype,那这两个有啥区别呢?通俗来说,就是单例还是多例。举个栗子:
先定义两个Bean:

@Scope("prototype")
@Component
public class PrototypeBean {
}
@Component
public class SingletonBean {
}

分别定义了一个prototype作用域和singleton作用域的Bean。

public class SpringStartMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfig.class);
        PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class);
        System.out.println(prototypeBean1);
        PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class);
        System.out.println(prototypeBean2);
        SingletonBean singletonBean1 = context.getBean(SingletonBean.class);
        System.out.println(singletonBean1);
        SingletonBean singletonBean2 = context.getBean(SingletonBean.class);
        System.out.println(singletonBean2);
    }
}

运行结果如下:

com.spring.transaction.demo.entity.PrototypeBean@17497425
com.spring.transaction.demo.entity.PrototypeBean@f0da945
com.spring.transaction.demo.entity.SingletonBean@4803b726
com.spring.transaction.demo.entity.SingletonBean@4803b726

从上面运行结果可以看到,从Spring容器中多次获取作用域为singleton的Bean都是同一个Bean,而获取的prototype的Bean每次都一样。也就是说,在Spring中,作用域为singleton的Bean只此一个,而prototype作用域的有很多个。为啥要这么做呢?因为创建对象一方面需要消耗性能,而方面需要开辟存储空间,如果对象过多可能会OOM,即便是进行垃圾回收,大量对象的创建回收无疑会导致过多的gc,最后导致程序变慢。对于一些不会更改(没有状态)的类完全没有必要定义成prototype,而实际业务中绝大多数的服务类都属于这种,因此Spring默认的作用域也是singleton。

As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.

singleton分析

在这里插入图片描述
那么singleton是如何起作用的呢?
我们可以在第二次获取SingletonBean类型Bean的地方打上断点:
// 首先会根据类型获取缓存的beanName(名称缓存在beanDefinitionNames列表中)
// 然后根据beanName去查找已经实例化好的Bean

// org.springframework.beans.factory.support.AbstractBeanFactory
	protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;
		// Eagerly check singleton cache for manually registered singletons.
		// 在此处会根据名称获取单例对象
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isDebugEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		// 此处省略大部分逻辑
		return (T) bean;
	}

// 从缓存中获取单例

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

在这里插入图片描述
从上图不难看出,此时在singletonFactories这个缓存中,已经缓存了大量的Bean定义。这个缓存是由ConcurrentHashMap来实现的,方便用于查找指定名称的Bean。
// 获取单例Bean之后就立刻返回了,以后每次需要获取SingletonBean类型的Bean,最终都会从这个缓存中获取,也就是都是同一个对象。
至于这个缓存是何时添加进来的呢?
我们不妨添加一个条件断点 如下:(因为放进去必然会调用singletonObjects的添加方法)
在这里插入图片描述
再次进行debug,如下所示:
在这里插入图片描述
从栈帧中不难看出,此时正是在Spring容器的刷新中进行所有非懒加载单例Bean实例化的时候。由于SingletonBean是一个单例Bean,所以也在此进行初始化。在初始化定义完成之后也会将这个Bean存放到单例对象缓存中。

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "'beanName' must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<Exception>();
				}
				try {
				    // 此时会调用 createBean(beanName, mbd, args)
					singletonObject = singletonFactory.getObject();
					// 当前属于一个新的单例Bean
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
				    //  在单例Bean创建完成之后,就会加入到单例缓存中
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

总结一下:Spring中是通过Map缓存来保证单例Bean的唯一的,第一次初始化的时候放入这个缓存,后面再次查找的时候都从这个缓存中获取。

Spring的单例与设计模式中的单例的不同之处:非硬编码 容器唯一而非一个类加载器对应一个实现
Spring’s concept of a singleton bean differs from the singleton pattern as defined in the Gang of Four (GoF) patterns book. The GoF singleton hard-codes the scope of an object such that one and only one instance of a particular class is created per ClassLoader. The scope of the Spring singleton is best described as being per-container and per-bean. This means that, if you define one bean for a particular class in a single Spring container, the Spring container creates one and only one instance of the class defined by that bean definition. The singleton scope is the default scope in Spring.

prototype分析

在这里插入图片描述
那么prototype是什么样的逻辑呢?
在第二次获取PrototypeBean的时候打上断点,进行debug
// 前面的逻辑跟单例Bean基本相同,首先根据类型去查找Bean的名称
// 仍然根据beanName到Bean工厂里doGetBean
// 此时也会去单例Bean缓存中进行获取,但是很遗憾,前面的人并没有给后人留下啥遗产
在这里插入图片描述
// 然后去父容器中查找,此时没有父容器,因此也无法获取
在这里插入图片描述
// 获取Bean定义
在这里插入图片描述
// 进行prototype类型bean的解析

else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
		// 这里会将beanName放到一个本地线程局部变量prototypesCurrentlyInCreation中
		beforePrototypeCreation(beanName);
		// 进行Bean的创建 createBean这个方法与单例bean创建的逻辑是一模一样的
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		// 从prototypesCurrentlyInCreation缓存中移除beanName
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

// 通过createBean创建一个实体Bean,此处逻辑和单例Bean一模一样,查找类的构造方法,根据构造方法创建实例,扫描注解,然后,嗯,不一样

// 这段单例工厂提前暴露prototype享受不到了
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isDebugEnabled()) {
		logger.debug("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, new ObjectFactory<Object>() {
		@Override
		public Object getObject() throws BeansException {
			return getEarlyBeanReference(beanName, mbd, bean);
		}
	});
}

接下来填充属性populateBean,初始化initializeBean

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
	if (System.getSecurityManager() != null) {
		AccessController.doPrivileged(new PrivilegedAction<Object>() {
			@Override
			public Object run() {
				invokeAwareMethods(beanName, bean);
				return null;
			}
		}, getAccessControlContext());
	}
	else {
		invokeAwareMethods(beanName, bean);
	}

	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	try {
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null),
				beanName, "Invocation of init method failed", ex);
	}

	if (mbd == null || !mbd.isSynthetic()) {
		// bean初始化后置处理 AOP逻辑 
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}
	return wrappedBean;
}

初始化Bean并返回
总结一下:从上面的逻辑可以看出,对于prototype类型的Bean没有机会将初始化好的实例放到单例对象缓存中或者某个其他的缓存,所以每次只能去创建一个新的实例

混合模式 singleton中包含prototype

修改类定义如下:

@Component
public class SingletonBean {
    @Autowired
    private PrototypeBean prototypeBean;

    public PrototypeBean getPrototypeBean() {
        return prototypeBean;
    }
}

那么按照如下方式获取单例里面的属性是否同一个对象呢?

 SingletonBean singletonBean1 = context.getBean(SingletonBean.class);
 SingletonBean singletonBean2 = context.getBean(SingletonBean.class);
 System.out.println(singletonBean1.getPrototypeBean());
 System.out.println(singletonBean2.getPrototypeBean());

运行结果如下:

com.spring.transaction.demo.entity.PrototypeBean@152aa092
com.spring.transaction.demo.entity.PrototypeBean@152aa092

由此可见在在单例Bean中的多例Bean也变成了单例,那么这是啥原理呢?
先分析一下,Spring中Bean在初始化的过程中,如果有属性需要注入,就会去调用对应getBean方法,然后设置到Bean属性中,也就是populateBean,对于单例Bean,就会调用初始化方法,初始化方法调完之后,也就完成了Bean创建的过程,这是会把创建好的单例Bean放入到单例对象缓存中,以后获取这个单例Bean的时候,都是缓存中的对象,因此其中的属性是不变的。所以这时prototype作用域的Bean就变成了“单例”了。

混合模式 prototype中包含SingletonBean
@Scope("prototype")
@Component
public class PrototypeBean {

    @Autowired
    private SingletonBean singletonBean;

    public SingletonBean getSingletonBean() {
        return singletonBean;
    }
}
PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class);
PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class);
System.out.println(prototypeBean1);
System.out.println(prototypeBean2);
System.out.println(prototypeBean1.getSingletonBean());
System.out.println(prototypeBean2.getSingletonBean());

运行结果如下:

com.spring.transaction.demo.entity.PrototypeBean@4ef37659
com.spring.transaction.demo.entity.PrototypeBean@776b83cc
com.spring.transaction.demo.entity.SingletonBean@37858383
com.spring.transaction.demo.entity.SingletonBean@37858383

这个也不难分析,虽然每次获取prototype类型Bean的时候都要调用getBean去实例化,此时注入singleton类型Bean,都是从缓存中取的,所以单例Bean仍然是单例Bean.

综上所述:是否属于同一个对象,就看这个prototype Bean触发了几次调用getBean,在singleton中的prototype类型Bean由于只在注入的时候触发了一次getBean操作,因此后面在这个单例Bean中只有一个了。( only gets one opportunity to set the properties)

参考:https://docs.spring.io/spring/docs/5.2.6.BUILD-SNAPSHOT/spring-framework-reference/core.html#beans-factory-scopes

那么,如果我们就想在单例Bean中使用prototype属性时,每次都获取一个新的,就不行了吗?当然可以,具体可以参照https://docs.spring.io/spring/docs/5.2.6.BUILD-SNAPSHOT/spring-framework-reference/core.html#beans-factory-method-injection
两种方式:Method Injection->Lookup Method Injection和Arbitrary Method Replacement,此处不继续介绍了、

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值