目录
一、@Scope原型、单例实现原理
1.@Scope配置单例
2.@Scope配置原型
3.实现原理
二、使用细节
1.原型失效示例
1.1 Controller
1.2 Service
2.原型成功示例
2.1 第一种生效方式
2.2 第二种生效方式
三、总结
一、@Scope原型、单例实现原理
在Spring框架中可以使用@Scope注解声明需要创建的bean是原型或者是单例类型的,如果是原型则每次调用getBean获取到的对象都是不同的;如果是单例则调用getBean方法获取的对象每个都是一样的。
1.@Scope配置单例
有两种配置方式,一种是不指定@Scope,直接使用@Component相关注解即可,一般都是使用这种方式指定单例对象:
@Service
public class XXXX {
...
}
另一种是指定@Scope注解,这种方式只要懂的@Component的默认bean属性基本上不会使用这种方式:
@Scope("singleton")
@Service
public class XXXX {
...
}
2.@Scope配置原型
如下:
@Scope("prototype")
@Service
public class XXXX {
...
}
或者:
@Configuration
public class SystemConfig {
@Scope("prototype")
@Bean
public TaskExecutor taskExecutor() {
...
}
}
3.实现原理
接下来简要介绍一下在getBean方法调用链中在哪个地方使用了原型和单例的配置属性。
其关键部分源码如下:
public abstract class AbstractBeanFactory
extends FactoryBeanRegistrySupport
implements ConfigurableBeanFactory {
// 单例模式创建的bean都会被缓存在该集合中
private final Map<String, Object> singletonObjects =
new ConcurrentHashMap<>(256);
@Override
public Object getBean(String name) throws BeansException {
// 其它的getBean略过,最终都会调用doGetBean方法
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name,
@Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly)
throws BeansException {
// getSingleton方法实际上会调用singletonObjects等一系列的单例对象
// 缓存集合来提前获取一次,因此在单例对象第一次实例化之后后续获取的
// 就是缓存集合中的对象了
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 如果是FactoryBean类型的将会在该方法中被处理,并调用getObject
// 方法获取bean对象,因此能够保证FactoryBean是单例的且getObject
// 方法返回的一直是同一个对象才能保证getBean返回的对象是同一个
bean = getObjectForBeanInstance(sharedInstance, name,
beanName, null);
} else {
...
if (mbd.isSingleton()) {
// 跑到这里则说明前面的getSingleton方法从缓存中获取到单例对象
// 意味着Spring工厂中还未创建该对象,因此该方法中最终会调用
// createBean方法创建bean对象并缓存到单例集合中,方便前面的
// getSingleton方法直接获取
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
// 处理被FactoryBean封装的baen类型
bean = getObjectForBeanInstance(sharedInstance, name,
beanName, mbd);
} else if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
// 调用createBean方法前设置prototypesCurrentlyInCreation
// 相当于为创建该原型Bean对象上锁
beforePrototypeCreation(beanName);
// 调用createBean方法去创建bean,并且不会缓存
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
// 释放prototypesCurrentlyInCreation,相当于释放锁
afterPrototypeCreation(beanName);
}
// 处理被FactoryBean封装的baen类型
bean = getObjectForBeanInstance(prototypeInstance, name,
beanName, mbd);
} else {
// 处理其它自定义Scope范围对象
...
}
...
}
...
}
}
二、使用细节
虽然使用@Scope声明了原型,这个bean在Spring工厂中也确实是原型,但还是有一些使用的小细节需要注意一下。
1.原型失效示例
当使用下面这种方式使用原型bean时,原型bean的声明在使用层面将会失效。
1.1 Controller
示例如下:
@Controller
public class TestController implements InitializingBean {
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testController Initializing");
}
}
1.2 Service
示例如下:
@Scope("prototype")
@Service
public class TestService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testService Initializing");
}
}
失效原因:如果使用这种配置方式,当调用controller的方法时testService对象将永远会是同一个,因为Controller是个单例,其只会在项目启动时被加载一次,自动注入Service时也只是注入一次,注入完这一次后将不再调用getBean设置该Controller的Service。因此对于Controller而言,这里面的Service就是一个单例对象(当然,如果其它的Controller自动注入了这个Service,两个Service确实是不一样的)。
2.原型成功示例
有两种实现方式:
- 一种是保证持有原型Service的Controller也是原型bean,这样每次请求进来的Controller和Service都是不一样的;
- 一种是手动调用getBean方法获取新的bean对象。
2.1 第一种生效方式
Controller实例如下:
@Controller
public class TestController implements InitializingBean, BeanFactoryAware {
private BeanFactory beanfactory;
@Override
public void setBeanFactory(BeanFactory beanFactory)
throws BeansException {
this.beanfactory = beanfactory ;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testController Initializing");
}
@GetMapping("/test.json")
public XXX request() {
TestService testService = beanFactory.getBean(TestService.class);
testService.XXXX();
return new XXX();
}
}
这种方式为手动获取bean工厂对象,每次需要调用原型bean时则使用getBean方法从Bean工厂中获取对应的新对象,进而完成调用。
2.2 第二种生效方式
Controller示例如下:
@Scope("prototype")
@Controller
public class TestController implements InitializingBean {
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testController Initializing");
}
}
Service不变,这样将能实现每次有新的请求进来时Controller和Service每次都是新的bean对象。
那么为什么可以实现这样的效果呢?原因便在SpringMVC的HandlerMethod类中,关键源码如下:
public class HandlerMethod {
// 可以是具体的bean对象,也可以是这个bean对象的beanName
private final Object bean;
// 设置进来的bean工厂
private final BeanFactory beanFactory;
public HandlerMethod createWithResolvedBean() {
// 当在DispatcherServlet中获取HandlerExecutionChain对象时,最终会调用
// 到这个方法中
Object handler = this.bean;
// 如果是String类型,则需要进入调用getBean方法获取bean对象
// 如果是单例这个bean则是对应的Controller对象,如果是原型则
// 是String字符串,是字符串每次都会调用getBean方法获取Controller对象
if (this.bean instanceof String) {
String beanName = (String) this.bean;
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler);
}
}
可以看到实际上Spring框架也是每次有新的请求都调用了一次getBean方法,在获取Controller原型时又会使用自动注入再注入进一个原型Service,让Spring框架帮我们完成了每次调用都是新的对象这个操作。
三、总结
从上面的例子可以总结出一下几点:
- 对于单例对象而言,在里面自动注入原型bean对象,对于单例对象而言,这个原型bean就是单例的,每次调用的都是同一个bean对象;
- 要达到每次使用都是新对象,必须得使用getBean方法重新获取新的对象;
- 可以将Controller也变成原型对象,使用Spring框架的特性来帮我们调用getBean方法获取原型对象,确保每次调用都是不同的对象。