目录
概述
Scope翻译过来就是作用域的意思,是描述bean实例的bean定义中重要组成部分之一。我们平时接触最多的就是singleton(单例)和prototype(多例)。Spring给我们提供了几个内置的Scope,当内置Scope无法满足需求时,Spring框架允许我们自定义Scope。
Spring中内置的Scope
scope | description |
---|---|
singleton | (默认)将单个bean定义作用于每个Spring IoC容器的单个对象实例 |
prototype | 将单个bean定义作用于任意数量的对象实例 |
request | 将单个bean定义的范围扩大到单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的后面创建的。只有在感知web的Spring ApplicationContext的上下文中才有效 |
session | 将单个bean定义作用于HTTP会话的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效 |
application | 将一个bean定义作用域作用到ServletContext的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效 |
websocket | 将一个bean定义定义到WebSocket的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效 |
Scope实现原理分析
Scope接口
public interface Scope {
/**
* 根据beanName获取作用域下的实例,如果没有,则从工厂中获取
* @param name beanName
* @param objectFactory 获取bean实例的工厂中获取实例并在作用域下创建
*/
Object get(String name, ObjectFactory<?> objectFactory);
...
}
Scope注解
public @interface Scope {
/**
* 指定作用域的名称scopeName.
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
*/
String value() default ConfigurableBeanFactory.SCOPE_SINGLETON;
...
}
ConfigurableBeanFactory接口
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
...
/**
* 注册Scope
* @param scopeName 作用域名称
* @param 实现了Scope接口的类
*/
void registerScope(String scopeName, Scope scope);
...
}
BeanDefinition接口
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
...
/**
* 获取作用域名称
* Return the name of the current target scope for this bean,
* or {@code null} if not known yet.
*/
String getScope();
...
}
当@Scope注解作用在类或方法时,Spring容器启动后会为每个bean生成bean定义,其中getSope()方法就会返回注解中value的值。
AbstractBeanFactory类
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
...
protected <T> T doGetBean(
...
//单例逻辑
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 多例逻辑
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 其它
else {
// 从beanDefination中获取作用域名称
String scopeName = mbd.getScope();
// 根据作用域名称找到注册的Scope
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
// 从作用域中获取实例
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
// 创建bean,执行bean的生命周期,因此每次执行返回实例不一样
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
...
}
}
当通过ApplicationContext.getBean()时会执行AbstractBeanFactory类中doGetBean()方法,如果bean的作用域不是单例和多例,就从作用域里取出bean实例。
自定义Scope
通过分析Scope源码后,我们可以自定义线程作用域ThreadScope,即同一个线程内多次getBean是同一个对象,不同线程之间getBean对象不一样。
定义一个线程Scope
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> local = ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> map = local.get();
Object value = map.get(name);
if(value == null) {
value = objectFactory.getObject();
map.put(name, value);
}
return value;
}
@Override
public Object remove(String name) {
Map<String, Object> map = local.get();
return map.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// nothing to do
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
主要看get方法逻辑,在threadlocal的map中取出beanName对应的实例,如果不存在,则从对象工厂中获取creatBean后的实例并设置到map里。
定义一个线程作用域的注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Scope("thread")
public @interface ThreadScope {
}
自定义线程作用域注解,作用域名称为thread,可以标记在类或方法上。
注册Scope
@Configuration
public class ScopeConfig {
@Bean
public CustomScopeConfigurer registerThreadScope() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("thread", new ThreadScope());
return configurer;
}
@Bean
@ThreadScope
public User user() {
return new User();
}
}
注册一个User对象的Bean并指定作用域,同时注册线程作用域,指定scopeName和Scope。CustomScopeConfigurer是Spring提供给我们注册作用域的类,它实现了BeanFactoryPostProcessor接口,当Spring容器启动时,会回调postProcessBeanFactory方法,通过调用beanFactory.registerScope方法完成自定义Scope的注册。当然我们也可以自定义一个类实现BeanFactoryPostProcessor类并注册成一个Bean,实现思路是一样的。
public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered {
@Nullable
private Map<String, Object> scopes;
private int order = Ordered.LOWEST_PRECEDENCE;
@Nullable
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
/**
* 设置自定义Scope
* Specify the custom scopes that are to be registered.
* <p>The keys indicate the scope names (of type String); each value
* is expected to be the corresponding custom {@link Scope} instance
* or class name.
*/
public void setScopes(Map<String, Object> scopes) {
this.scopes = scopes;
}
/**
* 添加自定义Scope
* Add the given scope to this configurer's map of scopes.
* @param scopeName the name of the scope
* @param scope the scope implementation
* @since 4.1.1
*/
public void addScope(String scopeName, Scope scope) {
if (this.scopes == null) {
this.scopes = new LinkedHashMap<>(1);
}
this.scopes.put(scopeName, scope);
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 通过beanFactory.registerScope方法注册自定义Scope
if (this.scopes != null) {
this.scopes.forEach((scopeKey, value) -> {
if (value instanceof Scope) {
beanFactory.registerScope(scopeKey, (Scope) value);
}
else if (value instanceof Class) {
Class<?> scopeClass = (Class<?>) value;
Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class");
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else if (value instanceof String) {
Class<?> scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader);
Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class");
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else {
throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" +
scopeKey + "] is not an instance of required type [" + Scope.class.getName() +
"] or a corresponding Class or String value indicating a Scope implementation");
}
});
}
}
}
测试
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeConfig.class);
new Thread(() -> {
System.out.println(applicationContext.getBean(User.class));
System.out.println(applicationContext.getBean(User.class));
}).start();
new Thread(() -> {
System.out.println(applicationContext.getBean(User.class));
System.out.println(applicationContext.getBean(User.class));
}).start();
}
}
从结果中可知,同一个线程内多次getBean返回的是同一个对象,不同线程之间getBean返回的是不同对象。Beautiful~~