Spring-Ioc之bean注入-scope

这里简单试验一下spring注入时的自定义scope

scope作用

spring-ioc支持bean的注入的同时,也支持定义bean的范围。而且范围的类型超出了java本身的类型以外,这些类型开箱即用,已经实现的范围类型有以下几种

ScopeDescription
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);
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值