面试题Spring Bean 的作用域
1. Spring 支持的 6 种标准作用域
在 Spring 框架中,Bean 的作用域决定了其实例的创建方式、生命周期和可见范围。以下是核心作用域:
作用域 | 描述 | 适用场景 |
Singleton | 默认作用域,整个 Spring 容器中仅存在一个实例,全局共享。 | 无状态对象(如工具类、服务类) |
Prototype | 每次请求 Bean 时都会创建新实例,不进行缓存。 | 有状态对象(如用户会话数据) |
Request | 每个 HTTP 请求创建一个新实例,仅在当前请求内有效(需 Web 环境)。 | HTTP 请求相关的数据(如表单参数) |
Session | 每个 HTTP Session 创建一个新实例,用户会话期间有效(需 Web 环境)。 | 用户登录状态、购物车信息 |
Application | 整个 Web 应用生命周期内共享一个实例,作用域为 ServletContext。 | 全局配置、缓存管理 |
WebSocket | 每个 WebSocket 会话创建一个实例,仅在 WebSocket 连接期间有效。 | 实时通信场景(如聊天室消息管理) |
注:
- globalSession 是 Portlet 应用中的全局会话作用域,现较少使用。
- Request、Session、Application 和 WebSocket 作用域需在 Web 环境下启用(如 Spring MVC)。
2. 如何自定义作用域
Spring 允许通过实现 Scope 接口创建自定义作用域,例如实现线程级作用域或分布式作用域。以下是实现步骤:
步骤 1:实现 Scope 接口
自定义作用域类需实现 get()、remove() 等方法管理 Bean 实例的生命周期:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class ThreadLocalScope implements Scope {
private ThreadLocal<Map<String, Object>> threadLocal = ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadLocal.get();
Object obj = scope.get(name);
if (obj == null) {
obj = objectFactory.getObject();
scope.put(name, obj);
}
return obj;
}
@Override
public Object remove(String name) {
return threadLocal.get().remove(name);
}
// 其他方法(如 registerDestructionCallback)可空实现
}
步骤 2:注册自定义作用域
在 Spring 配置类中注册作用域,并指定作用域名称(如 threadLocal):
@Configuration
public class AppConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerScope("threadLocal", new ThreadLocalScope());
}
}
步骤 3:使用自定义作用域
在 Bean 定义中通过 @Scope 注解指定自定义作用域:
@Component
@Scope("threadLocal")
public class ThreadLocalBean {
private String data;
// Getter/Setter 省略
}
3. 示例演示
场景:使用 ThreadLocalScope 实现线程级作用域,确保每个线程获取独立的 Bean 实例。
验证代码:
@RestController
public class ScopeDemoController {
@Autowired
private ApplicationContext context;
@GetMapping("/demo")
public String demo() {
// 每次请求(不同线程)获取新的 ThreadLocalBean 实例
ThreadLocalBean bean = context.getBean(ThreadLocalBean.class);
return "ThreadLocalBean HashCode: " + bean.hashCode();
}
}
结果:
- 同一线程多次调用 /demo 返回相同 HashCode。
- 不同线程调用返回不同 HashCode。
总结
- 标准作用域:优先选择 Singleton(无状态)和 Prototype(有状态),Web 相关作用域需结合具体场景。
- 自定义作用域:通过实现 Scope 接口扩展 Spring 容器的能力,适用于分布式锁、线程隔离等复杂场景。
- 面试注意点:明确作用域的生命周期、线程安全问题,并能结合源码(如 AbstractBeanFactory.doGetBean())解释实现原理。