前言
Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,并将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象。
1.@Scope注解概述
@Scope注解能够设置组件的作用域,Scope注解类的源码,如下所示
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 "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
从**@Scope**注解类的源码中可以看出,在@Scope注解中可以设置如下值:
- ConfigurableBeanFactory#SCOPE_PROTOTYPE
- ConfigurableBeanFactory#SCOPE_SINGLETON
- org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
- org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
查看一下ConfigurableBeanFactory接口的源码,发现在该接口中存在两个常量的定义
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
}
SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype
而SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session
备注:request和session作用域是需要Web环境来支持的,这两个值基本上使用不到,实例对象的作用域设置为request和session
request.setAttribute("key", object);
session.setAttribute("key", object);
2.单实例bean作用域
创建MainConfig02配置类,实例化一个Person对象,并将其放置在Spring容器中
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
public Person person() {
return new Person("liqb", 24);
}
}
编写测试方法,从Spring容器中按照id获取两个Person对象,并判断这两个对象是否是同一个对象
/**
* 测试单实例bean
* @author liqb
* @date 2023-04-23 09:47
*/
@Test
public void testSingleBean() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
// 获取到的这个Person对象默认是单实例的,因为在IOC容器中给我们加的这些组件默认都是单实例的,
// 所以说在这儿我们无论多少次获取,获取到的都是我们之前new的那个实例对象
Person person = (Person) applicationContext.getBean("person02");
Person person2 = (Person) applicationContext.getBean("person02");
System.out.println(person == person2);
}
测试结果:true
结论:对象在Spring容器中默认是单实例的,Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,都是直接将对象返回,而不必再创建新的实例对象了。
2.多实例bean作用域
将MainConfig02配置类中Person对象的作用域修改成prototype,如下所示。
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
// 通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Scope("prototype")
public Person person() {
return new Person("liqb", 24);
}
}
再次运行测试方法
测试结果:false
结论:当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等
3.单实例bean作用域何时创建对象
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
}
创建测试方法
/**
* 测试单实例bean何时创建对象
* @author liqb
* @date 2023-04-23 10:01
*/
@Test
public void testSingleBeanTime() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
}
输出的结果信息如下所示
结论:Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中。
/**
* 测试单实例bean何时创建对象
* @author liqb
* @date 2023-04-23 10:01
*/
@Test
public void testSingleBeanTime() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
Person person = (Person) applicationContext.getBean("person02");
Person person2 = (Person) applicationContext.getBean("person02");
System.out.println(person == person2);
}
输出的结果信息如下所示
结论:Spring容器在启动时,将单实例组件实例化之后,会即刻加载到Spring容器中,以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了。
4.多实例bean作用域何时创建对象?
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
//通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Scope("prototype")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
}
创建测试方法
/**
* 测试多实例bean何时创建对象
* @author liqb
* @date 2023-04-23 10:01
*/
@Test
public void testPrototypeBeanTime() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
}
输出的结果信息如下所示
结论:当向Spring容器中获取Person实例对象时,Spring容器才会实例化Person对象,再将其加载到Spring容器中去。
/**
* 测试多实例bean何时创建对象
* @author liqb
* @date 2023-04-23 10:01
*/
@Test
public void testPrototypeBeanTime() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
Person person = (Person) applicationContext.getBean("person02");
Person person2 = (Person) applicationContext.getBean("person02");
System.out.println(person == person2);
}
输出的结果信息如下所示
结论:当对象的Scope作用域为多实例时,每次向Spring容器获取对象时,它都会创建一个新的对象并返回。很显然,以上获取到的person和person2就不是同一个对象了,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等。
5.单实例bean注意的事项
单实例bean是整个应用所共享的,所以需要考虑到线程安全问题,在玩SpringMVC的时候,SpringMVC中的Controller默认是单例的,有
些开发者在Controller中创建了一些变量,那么这些变量实际上就变成共享的了,Controller又可能会被很多线程同时访问,这些线程并发去修
改Controller中的共享变量,此时很有可能会出现数据错乱的问题,所以使用的时候需要特别注意。
6.多实例bean注意的事项
多实例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,那么就会影响系统的性能,因此这个地方需要注意点。
7.自定义Scope
实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例
package com.tianxia.springannotation.config.configEntity;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 自定义scope
* @author liqb
* @date 2023-04-23 10:26
**/
public class CustomerScope implements Scope {
public static final String CUSTOMER_SCOPE = "thread";
private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
@Override
protected Object initialValue() {
return new HashMap<>();
}
};
/**
* 返回当前作用域中name对应的bean对象
* @author liqb
* @date 2023-04-23 10:29
* @param name 需要检索的bean对象的名称
* @param objectFactory 如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个bean对象
* @return
*/
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 获取bean对象
Object bean = beanMap.get().get(name);
if (Objects.isNull(bean)) {
bean = objectFactory.getObject();
beanMap.get().put(name, bean);
}
return bean;
}
/**
* 将name对应的bean对象从当前作用域中移除
* @author liqb
* @date 2023-04-23 10:43
* @param name bean名称
* @return
*/
@Override
public Object remove(String name) {
return this.beanMap.get().remove(name);
}
/**
* 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
* @param name bean名称
* @param callback 回调方法
*/
@Override
public void registerDestructionCallback(String name, Runnable callback) {
System.out.println(name);
}
/**
* 用于解析相应的上下文数据,比如request作用域将返回request中的属性
* @author liqb
* @date 2023-04-23 10:44
* @param key
* @return
*/
@Override
public Object resolveContextualObject(String key) {
return null;
}
/**
* 作用域的会话标识,比如session作用域的会话标识是sessionId
* @author liqb
* @date 2023-04-23 10:44
* @return
*/
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
创建一个配置类
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.config.configEntity.CustomerScope;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 10:45
**/
@Configuration
public class MainConfig03 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person03")
@Scope(CustomerScope.CUSTOMER_SCOPE)
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
}
编写测试方法
/**
* 测试自定义Scope
* @author liqb
* @date 2023-04-23 10:47
*/
@Test
public void testCustomScope() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig03.class);
// 向容器中注册自定义的Scope
applicationContext.getBeanFactory().registerScope(CustomerScope.CUSTOMER_SCOPE, new CustomerScope());
// 使用容器获取bean
for (int i = 0; i < 2; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread() + "," + applicationContext.getBean("person03"));
System.out.println(Thread.currentThread() + "," + applicationContext.getBean("person03"));
}).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
实验结果: