前言:
- Spring容器在创建bean对象时,一般默认创建的都是单例对象,在整个容器中仅仅只存在一个该类型的bean对象
- 若有需求是要求每次调用的bean对象都是不同的bean对象,则需要配置作用域为prototype
- 在Spring Web容器中,作用域则有request、session、application三种类型
- 有时候,Spring容器内置的作用域并不能满足我们的需求,则可以通过自定义作用域来扩展
案例源码:码云仓库的base-004
1、Scope属性
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="作用域" />
- scope值:有singleton、prototype、request、session、application等,默认值为singleton。而
request、session、application三者皆在Spring Web容器中才存在
- scope值:有singleton、prototype、request、session、application等,默认值为singleton。而
2、作用域singleton
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="singleton" />
-
scope值为singleton时,Spring容器创建的bean对象为单例对象,在该容器仅且只存在一个该类型的bean对象
-
Spring容器在启动时,会
自动创建好单例对象,放在容器中
,以供使用。注意:若bean元素的lazy值为true时,即懒加载,bean对象的创建则是在被使用时才创建,与作用域值为prototype时相同
-
案例
-
实体类
package com.spring.study; public class BeanScope { /** * bean对象的作用域的值,用于打印Spring容器创建bean对象的方式--单例 or 多例 */ private String beanScope; public String getBeanScope() { return beanScope; } public void setBeanScope(String beanScope) { this.beanScope = beanScope; } public BeanScope(String beanScope) { this.beanScope = beanScope; System.out.println(String.format("正在创建bean对象{作用域=%s,内存地址=%s}",beanScope,this)); } }
-
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 单例对象,scope值为singleton--> <bean id="beanScope1" class="com.spring.study.BeanScope" scope="singleton"> <constructor-arg index="0" value="singleton" /> </bean> </beans>
-
测试类
package com.spring.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestSingleton { /** * ClassPathXmlApplicationContext容器 */ static ClassPathXmlApplicationContext context; /** * junit提供--@BeforeClass:初始化方法,在所有的方法被调用之前仅且只执行一次 * 构建Spring容器,初始化bean对象 */ @BeforeClass public static void createBean(){ System.out.println("Spring容器准备启动......"); // 1、定义bean配置文件 String classPathXml = "classpath:applicationContext.xml"; // 2、创建ClassPathXmlApplicationContext容器,加载配置文件 context = new ClassPathXmlApplicationContext(classPathXml); System.out.println("Spring容器启动完毕!!!"); } /** * junit提供--@Test:测试方法 * 用于测试 */ @Test public void printResult(){ // 3、获取所有bean对象的bean名称 String[] beanNames = context.getBeanDefinitionNames(); // 4、多次获取bean对象,并打印输出bean对象的内存地址,来确认调用的是否是同一个对象 for (String beanName : beanNames){ for (int i = 1; i <= 2; i++){ System.out.println(String.format("第%d次调用%s对象,其内存地址:%s",i,beanName,context.getBean(beanName))); } } } }
-
运行结果
Spring容器准备启动...... 正在创建bean对象{作用域=singleton,内存地址=com.spring.study.BeanScope@29b5cd00} Spring容器启动完毕!!! 第1次调用beanScope1对象,其内存地址:com.spring.study.BeanScope@29b5cd00 第2次调用beanScope1对象,其内存地址:com.spring.study.BeanScope@29b5cd00
- 从打印结果来看,单例对象的初始化,是在Spring容器启动阶段完成的。多次调用的该类型对象,都是同一个对象
-
3、作用域prototype
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="prototype" />
-
scope值为prototype时,Spring容器创建的bean对象为多例对象,
每次从容器获得的该类型对象都是不同的,因为容器每次都会重新创建该类型的对象
-
案例
-
实体类同上例的BeanScope类
-
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 多例对象,scope值为prototype--> <bean id="beanScope2" class="com.spring.study.BeanScope" scope="prototype"> <constructor-arg index="0" value="prototype"/> </bean> </beans>
-
测试类同上例的TestSingleton类
-
运行结果
Spring容器准备启动...... Spring容器启动完毕!!! 正在创建bean对象{作用域=prototype,内存地址=com.spring.study.BeanScope@64485a47} 第1次调用beanScope2对象,其内存地址:com.spring.study.BeanScope@64485a47 正在创建bean对象{作用域=prototype,内存地址=com.spring.study.BeanScope@25bbf683} 第2次调用beanScope2对象,其内存地址:com.spring.study.BeanScope@25bbf683
- 从运行结果来看,
多例对象的初始化,是在被调用时才创建的。且每次都会重新创建新的对象
- 从运行结果来看,
-
4、作用域request、session、application
-
request
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="request" />
-
存在环境:Spring容器的web环境
-
当scope值为request时,
该容器会针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
-
-
session
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="session" />
-
存在环境:Spring容器的web环境
-
当scope值为session时,
每个会话会对应一个bean实例,不同的session对应不同的bean实例
-
-
application
-
基本格式:
<bean id="bean名称" class="完整类路径" scope="application" />
-
存在环境:Spring容器的web环境
-
当scope值为application时,
一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的
。但是也有区别:singleton是每个Spring容器中只有一个bean实例,不同的Spring容器中可以有同名的bean对象
;而application是所有的Spring容器中仅且只有一个bean对象,不可以存在其它同名的bean对象
-
5、自定义作用域
-
若内置的作用域不能满足需求的时候,可以自定义作用域
-
实现步骤:
-
第一步:实现org.springframework.beans.factory.config.Scope 接口,以此来自定义作用域
package org.springframework.beans.factory.config; import org.springframework.beans.factory.ObjectFactory; import org.springframework.lang.Nullable; public interface Scope { /** * 返回当前作用域中name对应的bean对象 * name:需要检索的bean名称 * objectFactory:如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象 **/ Object get(String name, ObjectFactory<?> objectFactory); /** * 将name对应的bean对象从当前作用域中移除 **/ @Nullable Object remove(String name); /** * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象 */ void registerDestructionCallback(String name, Runnable callback); /** * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。 */ @Nullable Object resolveContextualObject(String key); /** * 作用域的会话标识,比如session作用域将是sessionId */ @Nullable String getConversationId(); }
-
第二步:将自定义作用域注册到容器中,需要用到org.springframework.beans.factory.config.ConfigurableBeanFactory的registerScope方法
/** * 向容器中注册自定义的Scope * scopeName:作用域名称 * scope:作用域对象 */ void registerScope(String scopeName, Scope scope);
-
第三步:在配置文件中,指定bean元素的scope值为第二步注册到容器的自定义作用域名称scopeName
<bean id="bean名称" class="完整类路径" scope="自定义作用域名称"/>
-
-
自定义作用域的关键在于
Scope接口的get方法中是如何处理返回bean对象的
-
案例
-
需求:采用多线程来访问容器中的bean对象,实现不同线程的bean对象是不同的对象,相同线程的bean对象是相同的对象。并与singleton作对比
-
实体类为BeanScope类
-
自定义作用域类
package com.spring.study; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.Scope; import java.util.HashMap; import java.util.Map; public class ThreadScope implements Scope { /** * 自定义作用域Scope名称常量 */ public static final String THREAD_SCOPE = "thread_scope"; /** * 初始化线程局部变量 */ private final ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal(){ @Override protected Object initialValue() { System.out.println("正在初始化线程局部变量ThreadLocal......"); return new HashMap<>(); } }; /** * 返回当前作用域中name对应的bean对象 * @param name * @param objectFactory * @return */ @Override public Object get(String name, ObjectFactory<?> objectFactory) { System.out.println(String.format("正在获取该作用域中的%s对象......",name)); Object bean = threadLocal.get().get(name); if (bean == null){ System.out.println(String.format("%s对象不存在,正在创建中......",name)); bean = objectFactory.getObject(); threadLocal.get().put(name,bean); System.out.println(String.format("%s对象创建完毕!!!",name)); } System.out.println(String.format("已获取到%s对象,over!!!",name)); return bean; } @Override public Object remove(String name) { return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } }
-
测试类(在测试类中将自定义作用域注册到Spring容器中)
package com.spring.test; import com.spring.study.ThreadScope; import org.junit.Before; import org.junit.Test; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.concurrent.TimeUnit; public class TestThreadScope { /** * ClassPathXmlApplicationContext容器 */ ClassPathXmlApplicationContext context; /** * 在测试方法之前执行此方法 */ @Before public void registerThreadScope(){ System.out.println("Spring容器准备启动......"); // 1、设置配置文件的位置 String classPathXml = "classpath:applicationContext.xml"; // 2、手动创建容器 context = new ClassPathXmlApplicationContext(){ // 向容器中注册自定义的作用域 @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope(ThreadScope.THREAD_SCOPE,new ThreadScope()); super.postProcessBeanFactory(beanFactory); } }; // 3、设置配置文件位置 context.setConfigLocation(classPathXml); // 4、启动容器 context.refresh(); System.out.println("Spring容器启动完毕!!!"); } /** * 测试方法 * @throws InterruptedException */ @Test public void ThreadScopeTest() throws InterruptedException { // 获取容器中所有的bean对象的bean名称 String[] beanNames = context.getBeanDefinitionNames(); for (String beanName : beanNames) { for (int i = 0; i < 2; i++) { new Thread(() -> { for (int j = 1; j <= 2; j++) { System.out.println(String.format("当前线程:%s,第%d次获取%s对象,内存地址:%s", Thread.currentThread(), j, beanName,context.getBean(beanName))); } }).start(); //间隔1秒 TimeUnit.SECONDS.sleep(1); } } } }
-
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 单例对象,scope值为singleton--> <bean id="beanScope1" class="com.spring.study.BeanScope" scope="singleton"> <constructor-arg index="0" value="singleton" /> </bean> <!-- 自定义作用域--> <bean id="threadScope" class="com.spring.study.BeanScope" scope="thread_scope"> <constructor-arg index="0" value="thread_scope"/> </bean> </beans>
-
运行结果
Spring容器准备启动...... 正在创建bean对象{作用域=singleton,内存地址=com.spring.study.BeanScope@5f3a4b84} Spring容器启动完毕!!! 当前线程:Thread[Thread-1,5,main],第1次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84 当前线程:Thread[Thread-1,5,main],第2次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84 当前线程:Thread[Thread-2,5,main],第1次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84 当前线程:Thread[Thread-2,5,main],第2次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84 正在获取该作用域中的threadScope对象...... 正在初始化线程局部变量ThreadLocal...... threadScope对象不存在,正在创建中...... 正在创建bean对象{作用域=thread_scope,内存地址=com.spring.study.BeanScope@43b3a2ba} threadScope对象创建完毕!!! 已获取到threadScope对象,over!!! 当前线程:Thread[Thread-3,5,main],第1次获取threadScope对象,内存地址:com.spring.study.BeanScope@43b3a2ba 正在获取该作用域中的threadScope对象...... 已获取到threadScope对象,over!!! 当前线程:Thread[Thread-3,5,main],第2次获取threadScope对象,内存地址:com.spring.study.BeanScope@43b3a2ba 正在获取该作用域中的threadScope对象...... 正在初始化线程局部变量ThreadLocal...... threadScope对象不存在,正在创建中...... 正在创建bean对象{作用域=thread_scope,内存地址=com.spring.study.BeanScope@2432e026} threadScope对象创建完毕!!! 已获取到threadScope对象,over!!! 当前线程:Thread[Thread-4,5,main],第1次获取threadScope对象,内存地址:com.spring.study.BeanScope@2432e026 正在获取该作用域中的threadScope对象...... 已获取到threadScope对象,over!!! 当前线程:Thread[Thread-4,5,main],第2次获取threadScope对象,内存地址:com.spring.study.BeanScope@2432e026
- 从运行结果来看
- 自定义的作用域与singleton作用域的相同点:在
同一个线程
中,两者从容器中多次获取的同一个bean对象都是同一个对象
- 自定义的作用域与singleton作用域的不同点:在
不同线程
中,自定义作用域从容器中多次获取的同一个bean对象是不同的对象
,而singleton作用域从容器中多次获取的同一个bean对象始终是是同一个对象
- 自定义的作用域与singleton作用域的相同点:在
- 从运行结果来看
-