在Spring框架中,不同的Bean实例化时机对应不同的应用场景。这里将详细说明每种实例化时机及其适用的场景。
1. 单例作用域(Singleton Scope)
实例化时机:容器启动时实例化。
应用场景:
- 全局配置类:适用于需要全局唯一、共享的配置类。例如,数据库连接池配置、全局缓存管理器等。
- 无状态服务类:适用于无状态且线程安全的服务类,例如业务逻辑处理类或工具类。这些类不存储任何特定于客户端的数据,可以被多个请求共享使用。
示例代码:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
public class MyService {
public void performTask() {
// 业务逻辑
}
}
2. 原型作用域(Prototype Scope)
实例化时机:每次请求时实例化。
应用场景:
- 有状态的 Bean:适用于每次使用都需要一个新实例的有状态类,例如复杂的算法操作类或者需要保存临时数据的对象。
- 短生命周期对象:适用于生命周期较短的对象,每次使用都需要重新初始化。
示例代码:
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public MyService myService() {
return new MyService();
}
}
public class MyService {
public void performTask() {
// 业务逻辑
}
}
3. 延迟加载(Lazy Initialization)
实例化时机:第一次使用时实例化。
应用场景:
- 资源消耗大的 Bean:适用于创建成本高且不一定会用到的Bean,例如大型对象或初始化过程复杂的对象。
- 条件性 Bean:适用于只有在某些条件下才使用的Bean,这样可以减少初始启动时间和资源消耗。
示例代码:
@Configuration
public class AppConfig {
@Bean
@Lazy
public MyService myService() {
return new MyService();
}
}
public class MyService {
public MyService() {
// 可能包含大量初始化工作
}
public void performTask() {
// 业务逻辑
}
}
4. 请求作用域(Request Scope)和会话作用域(Session Scope)
实例化时机:
- 请求作用域:每次HTTP请求时实例化。
- 会话作用域:每次新的HTTP会话开始时实例化。
应用场景:
- 请求作用域:适用于Web应用中与每个HTTP请求相关的Bean,例如表单处理对象、请求上下文信息等。
- 会话作用域:适用于Web应用中需要在整个用户会话期间保持状态的Bean,例如购物车、用户会话信息等。
示例代码:
假设我们在Spring MVC环境中使用:
@Configuration
public class AppConfig {
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyRequestScopedService requestScopedService() {
return new MyRequestScopedService();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MySessionScopedService sessionScopedService() {
return new MySessionScopedService();
}
}
public class MyRequestScopedService {
// 每次HTTP请求都会生成新的实例
public void handleRequest() {
// 处理请求逻辑
}
}
public class MySessionScopedService {
// 每次新的HTTP会话会生成新的实例
public void manageSession() {
// 处理会话逻辑
}
}
5. 全局会话作用域(Global Session Scope)
实例化时机:每次新的Portlet全局会话开始时实例化(主要用于Portlet应用)。
应用场景:
- Portlet 环境:适用于Portlet应用程序中的全局会话Bean,需要在整个Portlet会话中保持状态。
示例代码:
@Configuration
public class AppConfig {
@Bean
@Scope(value = "globalSession", proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyGlobalSessionService globalSessionService() {
return new MyGlobalSessionService();
}
}
public class MyGlobalSessionService {
// 每次新的Portlet全局会话会生成新的实例
public void manageGlobalSession() {
// 处理全局会话逻辑
}
}
6. 自定义作用域
Spring允许开发者创建自定义作用域以满足特定需求。例如,用于实现更复杂的生命周期管理或跨多个应用程序的共享状态。
实例化时机:
- 根据自定义作用域的逻辑进行实例化,可灵活配置。
示例代码:
首先,定义一个自定义作用域:
public class CustomScopeConfigurer implements ScopeConfigurer {
@Override
public Map<String, Object> resolveContextualObject(String key) {
// 实现自定义逻辑来管理Bean的生命周期
return null;
}
}
然后,在Spring配置中注册这个自定义作用域:
@Configuration
public class AppConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
// 添加自定义作用域配置
return configurer;
}
@Bean
@Scope("customScope")
public MyCustomScopedService customScopedService() {
return new MyCustomScopedService();
}
}
public class MyCustomScopedService {
// 自定义作用域管理的Bean
public void performTask() {
// 业务逻辑
}
}
总结
不同的Bean实例化时机对应不同的应用场景。选择合适的实例化时机和作用域可以帮助优化应用程序的性能和资源使用,并提高代码的可维护性和扩展性。具体场景如下:
- 单例作用域(Singleton Scope) :适用于无状态服务类、全局配置类等。
- 原型作用域(Prototype Scope) :适用于有状态的Bean或短生命周期对象。
- 延迟加载(Lazy Initialization) :适用于资源消耗大的Bean或条件性Bean。
- 请求作用域(Request Scope) :适用于与每个HTTP请求相关的Bean。
- 会话作用域(Session Scope) :适用于需要在整个用户会话期间保持状态的Bean。
- 全局会话作用域(Global Session Scope) :适用于Portlet应用中的全局会话Bean。
- 自定义作用域:适用于需要特殊生命周期管理的Bean,通过自定义作用域实现特定需求。
通过合理使用这些实例化时机,可以使得Spring应用程序更加高效、易于维护和扩展。
思考题1:有状态的bean如何理解
当bean被设计为线程不安全并且会保存跨请求的数据时,可以理解这个bean是有状态的。
有状态的Bean的特点
- 持有数据:有状态的Bean通常包含一些实例变量,用于存储需要在多个方法调用间保持的数据。
- 非线程安全:由于每个客户端可能会修改Bean的状态,因此它们通常不是线程安全的,需要特别注意并发访问的问题。
- 短生命周期:为了避免状态冲突,有状态的Bean通常具有较短的生命周期,例如原型作用域、请求作用域等。
思考题2:Spring源码中哪些地方使用到原型作用域
在Spring源码中,原型作用域(Prototype Scope)被使用和支持的地方主要集中在Bean的定义和创建机制中。
以下是一些在Spring框架内部使用或体现原型作用域的关键部分:
1. BeanFactory 和 ApplicationContext
BeanFactory和它的子接口ApplicationContext是Spring容器的核心,它们负责管理Bean的生命周期,包括Bean的创建、初始化和销毁。
示例:
当你调用BeanFactory.getBean()方法并请求一个原型作用域的Bean时,Spring会每次都创建一个新的实例。这在以下方法中得到体现:
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, final Class<T> requiredType,
final Object[] args, boolean typeCheckOnly) throws BeansException {
// ... 其他代码 ...
// 检查在原型作用域下是否应该创建新的实例
if (mbd.isPrototype()) {
// 创建新的原型Bean实例
return (T) createBean(beanName, mbd, args);
}
// ... 其他代码 ...
}
在这段代码中,如果Bean定义的作用域为原型(scope="prototype"),Spring将调用createBean方法来创建一个新的实例。
2. DefaultListableBeanFactory
DefaultListableBeanFactory是Spring容器的一个具体实现类,包含了关于如何处理原型作用域的详细逻辑。
示例:
当创建原型作用域的Bean时,DefaultListableBeanFactory会确保每次请求都会创建一个新的实例:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
if (mbd.isPrototype()) {
// 原型Bean需要每次创建一个新的实例
return createPrototypeBean(beanName, mbd, args);
}
// 单例Bean只需创建一次
return super.createBean(beanName, mbd, args);
}
protected Object createPrototypeBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 具体创建新原型Bean实例的逻辑
// 包括实例化、依赖注入和初始化等步骤
}
3. Scope Interface and Scoping Mechanism
Scope接口允许自定义作用域的实现,其中包括对原型作用域的支持。在Spring默认实现中,会有一个处理原型作用域的默认实现:
public class SimpleBeanScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 每次返回一个新的实例
return objectFactory.getObject();
}
@Override
public Object remove(String name) {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// 原型Bean不需要注册销毁回调
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
4. Annotation Support
Spring中的原型作用域可以通过注解声明,比如@Scope("prototype")。@Scope注解由Spring的元数据处理工具类处理,如AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。
示例:
当Spring扫描和解析带有@Scope注解的类时,它会根据注解信息设置Bean定义的作用域:
// 在解析Bean定义的时候
if (scopeMetadataResolver != null) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(beanDefinition);
beanDefinition.setScope(scopeMetadata.getScopeName());
}
// 在将注解信息转化为Bean定义时
if (beanClass.isAnnotationPresent(Scope.class)) {
Scope scope = beanClass.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
}
思考题3:日常java web开发中,基本没看到指定scope的情况,为什么没问题
1. 单例作用域适用于大多数服务类
大部分服务类(Service)、数据访问对象(DAO)以及控制器(Controller)都是无状态的,即它们不保存跨请求或跨线程的状态。对于这种无状态的Bean,单例作用域非常合适,因为它们只需被创建一次,并且可以被多个客户端共享使用。
示例:
一个典型的服务类,比如用户服务类,不需要持有任何状态信息,仅仅是封装了业务逻辑:
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void performSomeLogic() {
// 无状态的服务方法
System.out.println("Performing some logic");
}
}
在这种情况下,无论多少次调用UserService,因为它没有状态,每个请求共享同一个实例是安全且高效的。
2. 单例作用域提高性能
单例作用域只在应用启动时创建一次Bean实例,这样可以减少对象创建和垃圾回收的开销,同时提高性能。在高并发环境下,减少对象的创建次数也是一种优化手段。
3. Spring的依赖注入机制支持单例Bean
Spring的依赖注入机制天然支持单例Bean,它能够自动管理这些Bean的生命周期,从而确保它们在容器内只有一个实例。这简化了开发者的工作,不需要手动管理对象的创建和销毁。