在spring中这个叫做bean的作用域,xml中定义bean的时候,可以通过scope属性指定bean的作用域,如:
当scope的值设置为singleton的时候,整个spring容器中只会存在一个bean实例,通过容器多次查找bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的都是同一个bean对象,singleton是scope的默认值,所以spring容器中默认创建的bean对象是单例的,通常spring容器在启动的时候,会将scope为singleton的bean创建好放在容器中(有个特殊的情况,当bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建),用的时候直接返回。
package com.yuan11;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @title: UserModel
* @Author yuan11
* @Date: 2022/6/1 23:09
* @Version 1.0
*/
@Getter
@Setter
public class UserModel {
private String name;
private int age;
public UserModel() {
this.name = "我是通过UserModel的无参构造方法创建的!";
}
public UserModel(String name, int age) {
this.name = name;
this.age = age;
}
public UserModel(String s) {
System.out.println(String.format("create BeanScopeModel,{sope=%s},{this=%s}", s, this));
}
}
上面构造方法中输出了一段文字,一会我们可以根据输出来看一下这个bean什么时候创建的,是从容器中获取bean的时候创建的还是容器启动的时候创建的。
上面构造方法中输出了一段文字,一会我们可以根据输出来看一下这个bean什么时候创建的,是从容器中获取bean的时候创建的还是容器启动的时候创建的。
package com.yuan11;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @title: TestUser
* @Author yuan11
* @Date: 2022/6/1 23:10
* @Version 1.0
*/
public class TestUser {
public static void main(String[] args) {
System.out.println("spring容器准备启动.....");
String beanXml = "classpath:bean.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println("spring容器启动完毕!");
System.out.println("---------单例bean,每次获取的bean实例都一样---------");
System.out.println(context.getBean("userModelBean"));
System.out.println(context.getBean("userModelBean"));
System.out.println(context.getBean("userModelBean"));
}
}
前3行的输出可以看出,UserModel的构造方法是在容器启动过程中调用的,说明这个bean实例在容器启动过程中就创建好了,放在容器中缓存着
最后3行输出的是一样的,说明返回的是同一个bean对象
单例bean使用注意
单例bean是整个应用共享的,所以需要考虑到线程安全问题,之前在玩springmvc的时候,springmvc中controller默认是单例的,有些开发者在controller中创建了一些变量,那么这些变量实际上就变成共享的了,controller可能会被很多线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题;所以使用的时候需要特别注意。
如果scope被设置为prototype类型的了,表示这个bean是多例的,通过容器每次获取的bean都是不同的实例,每次获取都会重新创建一个bean实例对象。
多例bean使用注意
多例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,会影响系统的性能,这个地方需要注意。
下面要介绍的3个:request、session、application都是在spring web容器环境中才会有的。
当一个bean的作用域为request,表示在一次http请求中,一个bean对应一个实例;对每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了,request作用域用在spring容器的web环境中,这个以后讲springmvc的时候会说,spring中有个web容器接口WebApplicationContext,这个里面对request作用域提供了支持,配置方式:
这个和request类似,也是用在web环境中,session级别共享的bean,每个会话会对应一个bean实例,不同的session对应不同的bean实例,springmvc中我们再细说。
全局web应用级别的作用域,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。
有时候,spring内置的几种sope都无法满足我们的需求的时候,我们可以自定义bean的作用域。
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();
}
/**
* 向容器中注册自定义的Scope
*scopeName:作用域名称
* scope:作用域对象
**/
void registerScope(String scopeName, Scope scope);
package com.yuan11;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @title: ThreadScope
* @Author yuan11
* @Date: 2022/6/6 23:46
* @Version 1.0
*/
public class ThreadScope implements Scope {
public static final String THREAD_SCOPE = "thread";//@1
private ThreadLocal
@1:定义了作用域的名称为一个常量thread,可以在定义bean的时候给scope使用
BeanScopeModel
package com.yuan11;
/**
* @title: BeanScopeModel
* @Author yuan11
* @Date: 2022/6/6 23:49
* @Version 1.0
*/
public class BeanScopeModel {
public BeanScopeModel(String beanScope) {
System.out.println(String.format("线程:%s,create BeanScopeModel,{sope=%s},{this=%s}", Thread.currentThread(), beanScope, this));
}
}
上面的构造方法中会输出当前线程的信息,到时候可以看到创建bean的线程。
package com.yuan11;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.concurrent.TimeUnit;
/**
* @title: ThreadScopeTest
* @Author yuan11
* @Date: 2022/6/6 23:52
* @Version 1.0
*/
public class ThreadScopeTest {
public static void main(String[] args) throws InterruptedException {
String beanXml = "classpath:beans-thread.xml";
//手动创建容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(){
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//向容器中注册自定义的scope
beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());//@1
super.postProcessBeanFactory(beanFactory);
}
};
//设置配置文件位置
context.setConfigLocation(beanXml);
//启动容器
context.refresh();
//使用容器获取bean
for (int i = 0; i < 2; i++) {//@2
new Thread(() -> {
System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
}).start();
TimeUnit.SECONDS.sleep(1);
}
}
}
> 注意上面代码,重点在@1,这个地方向容器中注册了自定义的ThreadScope。
> @2:创建了2个线程,然后在每个线程中去获取同样的bean 2次,然后输出,我们来看一下效果。
从输出中可以看到,bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。