在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为Bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。
Bean的五种作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为Bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。
下面就是Spring直接支持的五种作用域了,当然开发者也可以自己定制作用域。
作用域 | 作用域说明 |
---|---|
singleton | 容器中仅存在一个对象,默认值 |
prototype | 每调用一次getBean(),都返回一个新的对象 |
request | 每一个HTTP请求会产生一个Bean对象 |
session | 同一个Http Session共用一个Bean |
global session | 类似于seesion作用域,仅在portletweb应用中有意义 |
说明:request,session以及global session这三个作用域都是只有在基于web的SpringApplicationContext实现的(比如XmlWebApplicationContext)中才能使用。 如果开发者仅仅在常规的Spring IoC容器中比如ClassPathXmlApplicationContext中使用这些作用域,那么将会抛出一个IllegalStateException来说明使用了未知的作用域。
singleton
当定义一个Bean的作用域为singleton时,容器只会根据Bean定义来创建该Bean的唯一实例。这些唯一的实例会缓存到容器中,后续针对单例Bean的请求和引用,都会从这个缓存中拿到这个唯一的实例。
Singleton作用域是Spring中的缺省作用域。不需要在类上面加任何注解,如:
package com.morris.spring.entity.scope;
public class SingletonBean {
}
然后使用AnnotationConfigApplicationContext将SingletonBean注入到spring容器中:
package com.morris.spring.demo.annotation;
import com.morris.spring.entity.scope.SingletonBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SingletonBeanDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingletonBean.class);
System.out.println(applicationContext.getBean(SingletonBean.class));
System.out.println(applicationContext.getBean(SingletonBean.class));
}
}
运行结果如下:
com.morris.spring.entity.scope.SingletonBean@1af5db5
com.morris.spring.entity.scope.SingletonBean@1af5db5
可以发现两次的打印结果一致,说明获取的SingletonBean是同一个实例。
prototype
prototype指的就是每次请求Bean实例的时候,返回的都是新实例的Bean对象。这是基于线程安全性的考虑,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
下面的例子展示了如何定义一个原型的Bean:
package com.morris.spring.entity.scope;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {
}
然后使用AnnotationConfigApplicationContext将SingletonBean注入到spring容器中:
package com.morris.spring.demo.annotation;
import com.morris.spring.entity.scope.PrototypeBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class PrototypeBeanDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println(applicationContext.getBean(PrototypeBean.class));
System.out.println(applicationContext.getBean(PrototypeBean.class));
}
}
运行结果如下:
com.morris.spring.entity.scope.PrototypeBean@b0ed20
com.morris.spring.entity.scope.PrototypeBean@e24743
可以发现两次的打印结果不一致,说明获取的PrototypeBean不是同一个实例。
对于Prototype作用域的Bean,不管是同一个线程获取多次,还是不同的线程获取多次得到的都是一个新的实例。
与其他的作用域相比,Spring是不会完全管理原型Bean的生命周期的:Spring容器只会初始化,配置以及装载这些Bean,传递给Client。但是之后就不会再去管原型Bean之后的动作了。
也就是说,初始化生命周期回调方法在所有作用域的Bean是都会调用的,但是销毁生命周期回调方法在原型Bean是不会调用的。所以,客户端代码必须注意清理原型Bean以及释放原型Bean所持有的一些资源。可以通过使用自定义的BeanPostProcessor来让Spring释放掉原型Bean所持有的资源。
自定义scope
自定义scope需要失效Scope接口。
package com.morris.spring.entity.scope;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.Objects;
public class ThreadScope implements Scope {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object o = threadLocal.get();
if(Objects.isNull(o)) {
o = objectFactory.getObject();
threadLocal.set(o);
}
return o;
}
@Override
public Object remove(String name) {
Object o = threadLocal.get();
threadLocal.remove();
return o;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
自定义Score的使用:
package com.morris.spring.entity.scope;
import org.springframework.context.annotation.Scope;
@Scope("threadScope")
public class ThreadScopeBean {
}
然后使用AnnotationConfigApplicationContext将ThreadScopeBean注入到spring容器中:
package com.morris.spring.demo.annotation;
import com.morris.spring.entity.scope.ThreadScope;
import com.morris.spring.entity.scope.ThreadScopeBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ThreadScopeDemo {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.getBeanFactory().registerScope("threadScope", new ThreadScope());
applicationContext.register(ThreadScopeBean.class);
applicationContext.refresh();
System.out.println(applicationContext.getBean(ThreadScopeBean.class));
System.out.println(applicationContext.getBean(ThreadScopeBean.class));
Thread thread = new Thread(() -> System.out.println(applicationContext.getBean(ThreadScopeBean.class)));
thread.start();
thread.join();
}
}
运行结果如下:
com.morris.spring.entity.scope.ThreadScopeBean@1fe1d71
com.morris.spring.entity.scope.ThreadScopeBean@1fe1d71
com.morris.spring.entity.scope.ThreadScopeBean@5fb7c4
从运行结果可以发现,同一个线程获取的实例是同一个,不同的线程获取的实例不同。
源码分析prototype作用域bean的创建
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
// prototype类型的bean的实例化
Object prototypeInstance = null;
try {
// 将beanName加入到当前创建Bean池中
beforePrototypeCreation(beanName);
// 每次进来都创建一个新的bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
// 将beanName从当前创建Bean池中移除
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
自定义作用域bean的创建
将自定义的Scope注册到BeanFactory中:
org.springframework.beans.factory.support.AbstractBeanFactory#registerScope
public void registerScope(String scopeName, Scope scope) {
Assert.notNull(scopeName, "Scope identifier must not be null");
Assert.notNull(scope, "Scope must not be null");
if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
}
Scope previous = this.scopes.put(scopeName, scope);
if (previous != null && previous != scope) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
}
}
}
自定义作用域bean的创建过程:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
else {
// 自定义作用域
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
// 根据@Scope注解中的值获取自定义Scope
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 调用scope的get方法获得实例
// get方法中会传入一个ObjectFactory的lambda表达式,
// 在get方法里面可以自行决定是否需要调用ObjectFactory.getObject()来创建新的对象
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
// 创建一个新的bean
return createBean(beanName, mbd, args);
}
finally {
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);
}
}