这里简单试验一下spring注入时的自定义scope
scope作用
spring-ioc支持bean的注入的同时,也支持定义bean的范围。而且范围的类型超出了java本身的类型以外,这些类型开箱即用,已经实现的范围类型有以下几种
Scope | Description |
---|---|
singleton | (默认)将每个Spring IoC容器的bean definition范围限定为单个对象实例。 |
prototype | 将bean definition范围限定为任意数量的对象实例。 |
request | 将bean definition范围限定为单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的bean实例。 仅在Web感知Spring ApplicationContext的上下文中有效。 |
session | 将bean definition范围限定为HTTP会话的生命周期。 仅在Web感知Spring ApplicationContext的上下文中有效。 |
application | 将bean definition范围限定为ServletContext的生命周期。 仅在Web感知Spring ApplicationContext的上下文中有效。 |
websocket | 将bean definition范围限定为WebSocket的生命周期。 仅在Web感知Spring ApplicationContext的上下文中有效。 |
本问代码环境可以参考
https://blog.csdn.net/kaxu00/article/details/82349182
scope接口源码
public interface Scope
package org.springframework.beans.factory.config;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;
/**
ConfigurableBeanFactory使用的策略接口,表示保存bean实例的目标作用域。
这允许使用为特定键注册的自定义其他作用域扩展BeanFactory的标准作用域“singleton”
和“prototype”。
即使它主要用于Web环境中的扩展作用域,这个SPI也是完全通用的:它提供了从任何底层
存储机制获取和放置对象的能力,例如HTTP会话或自定义会话机制。传递给此类的get和
remove方法的名称将标识当前范围中的目标对象。
范围实现应该是线程安全的。如果需要,一个Scope实例可以同时与多个bean工厂一起使用
(除非它明确地想要知道包含BeanFactory),任意数量的线程可以从任意数量的工厂同时
访问Scope。
*/
public interface Scope {
/**
从范围内返回具有给定名称的对象,如果在范围内存储机制中找不到则创建它。
这是Scope的核心操作,也是绝对必需的唯一操作。
参数
name:要检索的对象的名称
objectFactory - 如果范围内存储机制中不存在,则用于创建范围对象的ObjectFactory
返回:
所需的对象(永不为null)
*/
Object get(String name, ObjectFactory<?> objectFactory);
/**
从范围中删除具有给定名称的对象。
如果没有找到对象,则返回null; 否则返回已删除的Object。
请注意,实现还应删除指定对象的已注册销毁回调(如果有)。 但是,在这种情况下,
它确实不需要执行已注册的销毁回调,因为对象将被调用者销毁(如果适用)。
注意:这是可选操作。 如果实现不支持显式删除对象,则可能会抛出UnsupportedOperationException。
参数:
name - 要删除的对象的名称
返回:
已删除的对象,如果没有对象,则返回null
抛出:
IllegalStateException - 如果范围(this scope)当前不处于使用状态
*/
@Nullable
Object remove(String name);
/**
注册要在销毁范围内的指定对象时执行的回调(或者在整个范围销毁时,如果范围不破坏单个对象,而只是完全终止)。
注意:这是可选操作。只有具有实际销毁配置的scoped bean(DisposableBean,destroy-method,DestructionAwareBeanPostProcessor)才会调用此方法。实现应该尽力在适当的时间执行给定的回调。如果底层运行时环境根本不支持这样的回调,则必须忽略回调并记录相应的警告。
请注意,“销毁”是指作为作用域自身生命周期的一部分自动销毁对象,而不是应用程序已明确删除的单个作用域对象。如果通过此facade的remove(String)方法删除了作用域对象,则应删除任何已注册的销毁回调,假设删除的对象将被重用或手动销毁。
参数:
name - 要为其执行销毁回调的对象的名称
callback - 要执行的销毁回调。请注意,传入的Runnable永远不会抛出异常,因此可以
安全地执行它而无需封闭的try-catch块。此外,Runnable通常是可序列化的,前提是它的目标对象也是可序列化的。
抛出:
IllegalStateException - 如果基础范围当前不活动
*/
void registerDestructionCallback(String name, Runnable callback);
/**
解析给定键的上下文对象(如果有)。 例如。 键“request”的HttpServletRequest对象。
*/
@Nullable
Object resolveContextualObject(String key);
/**
返回当前基础范围的对话ID(如果有)。
会话ID的确切含义取决于其存储机制。 在会话范围的对象的情况下,会话ID通常等于
(或从其导出)会话ID; 对于位于整个会话中的自定义对话,当前对话的特定ID将是合适的。
注意:这是可选操作。 如果存储机制没有明显的此类ID候选者,则在此方法的实现中返回
null是完全有效的。
返回:
会话ID,如果当前作用域没有会话ID,则为null
抛出:
IllegalStateException - 如果基础范围当前不活动
*/
@Nullable
String getConversationId();
}
看一个spring自带的实现
public class SimpleThreadScope implements Scope
package org.springframework.context.support;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.Nullable;
/*
一个简单的线程支持的Scope实现。
注意:默认情况下,此线程作用域未在常见上下文中注册。 相反,您需要通过org.springframework.
beans.factory.config.ConfigurableBeanFactory.registerScope或通过org.springframework.
beans.factory.config.CustomScopeConfigurer bean将其显式分配给设置中的范围键。
SimpleThreadScope不会清除与其关联的任何对象。 因此,通常最好在Web环境中使用RequestScope。
有关支持销毁回调的基于线程的Scope的实现,请参阅Spring by Example自定义线程范围模块。
*/
public class SimpleThreadScope implements Scope {
private static final Log logger = LogFactory.getLog(SimpleThreadScope.class);
//这里其实用的将一个HashMap对象植入到ThreadLocal中来简单实现Scope
private final ThreadLocal<Map<String, Object>> threadScope =
new NamedThreadLocal<Map<String, Object>>("SimpleThreadScope") {
@Override
protected Map<String, Object> initialValue() {
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = this.threadScope.get();
Object scopedObject = scope.get(name);
//按照接口的约定,如果当前范围找不到对象,则放入objectFactory.getObject()
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
scope.put(name, scopedObject);
}
//返回值永远不会为空
return scopedObject;
}
@Override
@Nullable
public Object remove(String name) {
Map<String, Object> scope = this.threadScope.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
logger.warn("SimpleThreadScope does not support destruction callbacks. " +
"Consider using RequestScope in a web environment.");
}
@Override
@Nullable
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
//这里返回的是当前线程的ID
return Thread.currentThread().getName();
}
}
demo
修改之前使用的ConfigTest类
@Configuration
public class ConfigTest {
//这里用来配置自定义的scope,使其注入到上下文中
@Bean
public CustomScopeConfigurer getCustomScopeConfigurer(){
CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
customScopeConfigurer.addScope("SimpleThreadScope",new SimpleThreadScope());
return customScopeConfigurer;
}
@Bean
public BeanTest getBeanTest(){
BeanTest beanTest = new BeanTest();
beanTest.setName("ok");
return beanTest;
}
//这里创建一个scope为“SimpleThreadScope”的bean工厂,用来创建线程级的bean
@Bean
@Scope("SimpleThreadScope")
public BeanTest getBeanTestByThread(){
BeanTest beanTest = new BeanTest();
beanTest.setName("SimpleThreadScope ok");
System.out.println("~~~~~~~~~~SimpleThreadScope ok~~~~~~~~~~~~~~");
return beanTest;
}
}
分析
1.应用中不使用自定义scope的情况
在不修改DemoApplication(启动类)的情况下。并不会输出"~~~~~~~~~~~~ SimpleThreadScope ok ~~~~~~~~~~~~"。这是因为新的bean工厂并非默认的单例模式,不会在上下文启动的时候自动去创建bean对象,只有在真正使用的时候才会去创建。
2.应用中使用自定义scope的情况
修改一下启动类
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
System.out.println("~~~~~~~~~~ok~~~~~~~~~~~~~~");
//这里尝试两次调用去获取线程级别的bean对象。
ctx.getBean("getBeanTestByThread");
ctx.getBean("getBeanTestByThread");
//输出结果只会有一次
//"~~~~~~~~~~SimpleThreadScope ok~~~~~~~~~~~~~~"
//这正好对应了基于线程绑定的scope的设定
}
对自定义scope的调用是发生在AbstractBeanFactory的一组判断里面
// AbstractBeanFactory
// line 343行
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//这里在调用scope.get方法的时候,对第二个参数ObjectFactory
//创建了一个实现类,因为这个接口只有一个方法,所以这里利用java8
//的语法,默认实现了这个T getObject()方法
Object scopedInstance = scope.get(beanName, () -> {
//这里执行回调 Callback before prototype creation
//这个方法是对正在创建的原型类对象进行注册,注入到
//prototypesCurrentlyInCreation这个集合中去
beforePrototypeCreation(beanName);
try {
//根据SimpleThreadScope中get方法的逻辑。如果scope找不到
//对应name的对象,就会执行这里的createBean(beanName, mbd, args)
return createBean(beanName, mbd, args);
}
finally {
//同样执行回调
//Callback after prototype creation.
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);
}
}