Spring Bean作用域:为什么Spring Bean需要多种作用域?
一个beanFactory对象中bean的名称和id是唯一的,但是当BeanFactory设置了parentBeanFactory时即层级beanFactory时BeanFactory和parentBeanFactory可能存在重复的bean
作用域中最为主要的是singleton和prototype , request session application主要是针对selvert模板引擎所使用的,但是现在项目开发中大部分使用的前后端分类,因此这三种使用的并不多.
“singleton” Bean作用域:单例Bean在当前Spring应用真是唯一的吗?
作用于的单例主要是针对BeanDefiniton中的原信息是否单例,并不是依赖来源中的单例对象作为依赖来源的对象(注册单例对象的方法SingletonBeanRegistry#registerSingleton)
isSingleton和isPrototype不互斥?如果不互斥同时是这两种会是什么情况?
逻辑上,只能有一个为 true,但是两个都能为 false。
通常在 Bean 创建时,isSingleton 方法先判断,isPrototype 后判断,详情请参考 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法
“prototype” Bean作用域:原型Bean在哪些场景下会创建新的实例?
public class BeanScopeDemo implements DisposableBean {
@Bean
// 默认 scope 就是 "singleton"
public static User singletonUser() {
return createUser();
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
return createUser();
}
private static User createUser() {
User user = new User();
user.setId(System.nanoTime());
return user;
}
@Autowired
@Qualifier("singletonUser")
private User singletonUser;
@Autowired
@Qualifier("singletonUser")
private User singletonUser1;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser1;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser2;
@Autowired
private Map<String, User> users;
@Autowired
private ConfigurableListableBeanFactory beanFactory; // Resolvable Dependency
public static void main(String[] args) {
// 创建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 注册 Configuration Class(配置类) -> Spring Bean
applicationContext.register(BeanScopeDemo.class);
applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.printf("%s Bean 名称:%s 在初始化后回调...%n", bean.getClass().getName(), beanName);
return bean;
}
});
});
// 启动 Spring 应用上下文
applicationContext.refresh();
// 结论一:
// Singleton Bean 无论依赖查找还是依赖注入,均为同一个对象
// Prototype Bean 无论依赖查找还是依赖注入,均为新生成的对象
// 结论二:
// 如果依赖注入集合类型的对象,Singleton Bean 和 Prototype Bean 均会存在一个
// Prototype Bean 有别于其他地方的依赖注入 Prototype Bean
// 结论三:
// 无论是 Singleton 还是 Prototype Bean 均会执行初始化方法回调
// 不过仅 Singleton Bean 会执行销毁方法回调
//scopedBeansByLookup(applicationContext);
scopedBeansByInjection(applicationContext);
// 显示地关闭 Spring 应用上下文
applicationContext.close();
}
private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {
for (int i = 0; i < 3; i++) {
// singletonUser 是共享 Bean 对象
User singletonUser = applicationContext.getBean("singletonUser", User.class);
System.out.println("singletonUser = " + singletonUser);
// prototypeUser 是每次依赖查找均生成了新的 Bean 对象
User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
System.out.println("prototypeUser = " + prototypeUser);
}
}
private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {
BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println("beanScopeDemo.singletonUser = " + beanScopeDemo.singletonUser);
System.out.println("beanScopeDemo.singletonUser1 = " + beanScopeDemo.singletonUser1);
System.out.println("beanScopeDemo.prototypeUser = " + beanScopeDemo.prototypeUser);
System.out.println("beanScopeDemo.prototypeUser1 = " + beanScopeDemo.prototypeUser1);
System.out.println("beanScopeDemo.prototypeUser2 = " + beanScopeDemo.prototypeUser2);
System.out.println("beanScopeDemo.users = " + beanScopeDemo.users);
}
@Override
public void destroy() throws Exception {
System.out.println("当前 BeanScopeDemo Bean 正在销毁中...");
this.prototypeUser.destroy();
this.prototypeUser1.destroy();
this.prototypeUser1.destroy();
this.prototypeUser2.destroy();
// 获取 BeanDefinition
for (Map.Entry<String, User> entry : this.users.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition.isPrototype()) { // 如果当前 Bean 是 prototype scope
User user = entry.getValue();
user.destroy();
}
}
System.out.println("当前 BeanScopeDemo Bean 销毁完成");
}
}
prototype的bean 不完全受Springbean生命周期的管控。
“request” Bean作用域:request Bean会在每次HTTP请求创建新的实例吗?
这个配置主要是使用在给前端页面渲染,入jsp中. springbean对象本身每次请求都会改变,返回后的代理对象是不变.
要是用这个作用于是要项目问一个web项目,并且有serlvet
“session” Bean作用域:session Bean在Spring MVC场景下存在哪些局限性?
Spring 将 Bean 的作用域分为三种,singleton、prototype、自定义 Scope,在 AbstractBeanFactory#doGetBean 创建 bean 时根据三种情况分别创建对象。其中 singleton、prototype 是 Spring IoC 内置的,自定义 Scope 需要实现 Scope 接口,通过 get 方法创建对象。 request/session 这两种自定义 Scope 是为了解决 web 场景,RequestScope/SessionScope 会将创建的对象和 HttpRequest/HttpSession 绑定在一起。
session作用于会通过org.springframework.web.context.request.SessionScope#get实现
public Object get(String name, ObjectFactory<?> objectFactory) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex(); //互斥锁
// 通过互斥锁解决多线程不安全问题
synchronized(mutex) {
return super.get(name, objectFactory);
}
}
由于用户有可能会在同一个浏览器中打开多个tab页签访问用一个url,这时候他们所带的cookie信息是一样,因此会认为是用一个session,这样就会在成一个线程不安全的情况.为了解决这个问题,在获取session对象的会后,在SessionScope#get方法中增加互斥锁来解决该问题,
“application” Bean作用域:application Bean是否真的有必要?
自定义Bean作用域:设计Bean作用域应该注意哪些原则?
ThreadLocal 级别 Scope
ublic class ThreadLocalScope implements Scope {
public static final String SCOPE_NAME = "thread-local";
private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal("thread-local-scope") {
public Map<String, Object> initialValue() {
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 非空
Map<String, Object> context = getContext();
Object object = context.get(name);
if (object == null) {
object = objectFactory.getObject();
context.put(name, object);
}
return object;
}
@NonNull
private Map<String, Object> getContext() {
return threadLocal.get();
}
@Override
public Object remove(String name) {
Map<String, Object> context = getContext();
return context.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// TODO
}
@Override
public Object resolveContextualObject(String key) {
Map<String, Object> context = getContext();
return context.get(key);
}
@Override
public String getConversationId() {
Thread thread = Thread.currentThread();
return String.valueOf(thread.getId());
}
}
面试题