核心概念
Bean作用域(Scope) 定义了Spring容器如何创建和管理Bean实例。不同的作用域决定了Bean的生命周期、实例数量以及共享方式。
Spring支持多种作用域,主要分为两大类:
- 核心容器作用域:singleton、prototype
- Web应用作用域:request、session、application、websocket
Spring支持的作用域
1. singleton(单例)- 默认
特点:
- 整个Spring容器中只有一个Bean实例
- 容器启动时创建(非懒加载)
- 所有对该Bean的请求都返回同一个实例
- 容器关闭时销毁
@Component
@Scope("singleton") // 默认值,可以省略
public class SingletonBean {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
// 测试
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
SingletonBean bean1 = context.getBean(SingletonBean.class);
SingletonBean bean2 = context.getBean(SingletonBean.class);
System.out.println(bean1 == bean2); // true,同一个实例
bean1.increment();
System.out.println(bean2.getCounter()); // 1,共享状态
使用场景:
- 无状态的Service、DAO层
- 工具类、配置类
- 需要全局共享的对象
线程安全考量:
- 单例Bean默认不是线程安全的
- 避免使用可变的成员变量
- 如需共享状态,使用
ThreadLocal或同步机制
2. prototype(原型)
特点:
- 每次请求都创建新实例
- 不会缓存
- Spring不负责销毁(不会调用destroy方法)
@Component
@Scope("prototype")
public class PrototypeBean {
private String id = UUID.randomUUID().toString();
public String getId() {
return id;
}
}
// 测试
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
System.out.println(bean1 == bean2); // false,不同实例
System.out.println(bean1.getId()); // 不同的UUID
System.out.println(bean2.getId()); // 不同的UUID
使用场景:
- 有状态的Bean
- 需要每次都创建新实例的对象
- 线程不安全但需要多线程使用的对象
注意事项:
// ❌ 错误:单例Bean注入原型Bean,原型失效
@Component
public class SingletonService {
@Autowired
private PrototypeBean prototypeBean; // 只会注入一次
}
// ✅ 正确:使用方法注入或ObjectFactory
@Component
public class SingletonService {
@Autowired
private ApplicationContext context;
public void doSomething() {
// 每次都获取新实例
PrototypeBean bean = context.getBean(PrototypeBean.class);
}
}
// ✅ 正确:使用ObjectFactory
@Component
public class SingletonService {
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanFactory;
public void doSomething() {
PrototypeBean bean = prototypeBeanFactory.getObject();
}
}
3. request(请求作用域)
特点:
- 每个HTTP请求创建一个Bean实例
- 请求结束时销毁
- 仅在Web应用中有效
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
private String requestId = UUID.randomUUID().toString();
public String getRequestId() {
return requestId;
}
}
@RestController
public class TestController {
@Autowired
private RequestBean requestBean;
@GetMapping("/test")
public String test() {
// 同一个请求内,requestBean是同一个实例
return requestBean.getRequestId();
}
}
proxyMode说明:
TARGET_CLASS:使用CGLIB代理INTERFACES:使用JDK动态代理- 必须使用代理,否则单例Bean无法注入request作用域的Bean
使用场景:
- 存储请求级别的数据(用户信息、请求参数等)
- 替代ThreadLocal的场景
4. session(会话作用域)
特点:
- 每个HTTP Session创建一个Bean实例
- Session销毁时Bean也销毁
- 仅在Web应用中有效
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean {
private String sessionId = UUID.randomUUID().toString();
private User currentUser;
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
}
@RestController
public class UserController {
@Autowired
private SessionBean sessionBean;
@PostMapping("/login")
public String login(@RequestBody User user) {
sessionBean.setCurrentUser(user);
return "登录成功";
}
@GetMapping("/profile")
public User getProfile() {
// 同一个会话内,sessionBean是同一个实例
return sessionBean.getCurrentUser();
}
}
使用场景:
- 用户登录状态
- 购物车数据
- 用户个性化设置
5. application(应用作用域)
特点:
- 每个ServletContext创建一个Bean实例
- 整个Web应用共享
- 类似singleton,但范围是ServletContext
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationBean {
private AtomicInteger visitCount = new AtomicInteger(0);
public int incrementAndGet() {
return visitCount.incrementAndGet();
}
}
@RestController
public class StatisticsController {
@Autowired
private ApplicationBean applicationBean;
@GetMapping("/visit")
public String visit() {
int count = applicationBean.incrementAndGet();
return "总访问次数: " + count;
}
}
使用场景:
- 全局配置信息
- 应用级别的缓存
- 统计数据
6. websocket(WebSocket作用域)
特点:
- 每个WebSocket连接创建一个Bean实例
- WebSocket关闭时销毁
- 需要Spring WebSocket支持
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketBean {
private String connectionId = UUID.randomUUID().toString();
public String getConnectionId() {
return connectionId;
}
}
作用域对比表
| 作用域 | 实例数量 | 生命周期 | 是否线程安全 | 使用场景 |
|---|---|---|---|---|
| singleton | 1个 | 容器启动到关闭 | 否(需注意) | 无状态Service |
| prototype | 每次创建 | 由调用者管理 | 是(每次新建) | 有状态Bean |
| request | 每请求1个 | 请求开始到结束 | 是(请求隔离) | 请求级数据 |
| session | 每会话1个 | 会话创建到销毁 | 是(会话隔离) | 用户会话数据 |
| application | 1个 | ServletContext生命周期 | 否(需注意) | 应用级共享数据 |
| websocket | 每连接1个 | 连接建立到断开 | 是(连接隔离) | WebSocket数据 |
源码分析
Scope注解定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
// 代理模式
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
作用域管理
// AbstractBeanFactory.doGetBean()
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
String beanName = transformedBeanName(name);
Object bean;
// 1. 尝试从单例缓存获取
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// 2. 检查是否是prototype循环依赖
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 3. 根据作用域创建Bean
if (mbd.isSingleton()) {
// 单例
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args);
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// 原型
Object prototypeInstance = createBean(beanName, mbd, args);
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
// 自定义作用域(request、session等)
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, () -> {
return createBean(beanName, mbd, args);
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
return (T) bean;
}
实战示例
示例1:单例Bean的线程安全问题
// ❌ 不安全的单例Bean
@Service
public class UnsafeService {
private int count = 0; // 共享可变状态
public void increment() {
count++; // 非线程安全
}
}
// ✅ 安全的单例Bean
@Service
public class SafeService {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 线程安全
}
}
// ✅ 使用ThreadLocal
@Service
public class ThreadLocalService {
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
public void increment() {
count.set(count.get() + 1);
}
public int getCount() {
return count.get();
}
}
示例2:原型Bean的正确使用
@Configuration
public class BeanConfig {
// 原型Bean
@Bean
@Scope("prototype")
public PrototypeTask prototypeTask() {
return new PrototypeTask();
}
}
@Service
public class TaskService {
@Autowired
private ApplicationContext context;
public void executeTasks(int count) {
for (int i = 0; i < count; i++) {
// 每次获取新实例
PrototypeTask task = context.getBean(PrototypeTask.class);
task.execute();
}
}
}
示例3:Web作用域的完整应用
// Request作用域Bean
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String requestId;
private LocalDateTime requestTime;
private String userIp;
@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
this.requestTime = LocalDateTime.now();
}
// Getters and Setters
}
// Session作用域Bean
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private User currentUser;
private List<String> recentPages = new ArrayList<>();
public void addPage(String page) {
recentPages.add(page);
if (recentPages.size() > 10) {
recentPages.remove(0);
}
}
// Getters and Setters
}
// Controller使用
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
private RequestContext requestContext;
@Autowired
private UserSession userSession;
@GetMapping("/info")
public Map<String, Object> getInfo() {
userSession.addPage("/api/info");
Map<String, Object> result = new HashMap<>();
result.put("requestId", requestContext.getRequestId());
result.put("user", userSession.getCurrentUser());
result.put("recentPages", userSession.getRecentPages());
return result;
}
}
性能与最佳实践
1. 作用域选择原则
// 无状态 → singleton
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id);
}
}
// 有状态 → prototype
@Component
@Scope("prototype")
public class OrderProcessor {
private Order currentOrder;
public void process(Order order) {
this.currentOrder = order;
// 处理逻辑
}
}
2. 性能考虑
- singleton:性能最好,只创建一次
- prototype:每次创建都有开销,避免频繁获取
- request/session:需要代理,有轻微性能损耗
3. 内存管理
// 注意:原型Bean不会被Spring销毁,需要手动管理
@Component
@Scope("prototype")
public class ResourceBean implements DisposableBean {
private Connection connection;
@Override
public void destroy() {
// Spring不会自动调用,需要手动管理
if (connection != null) {
connection.close();
}
}
}
面试总结
关键要点
- 默认作用域是singleton,容器中只有一个实例
- prototype每次创建新实例,Spring不负责销毁
- Web作用域需要proxyMode,才能注入到单例Bean
- 单例Bean不是线程安全的,需要注意可变状态
- 单例注入原型会失效,需要使用特殊方法
常见面试题
Q1: 单例Bean是线程安全的吗?
A: 不是。单例Bean在多线程环境下共享,如果有可变状态需要做同步处理。
Q2: 如何在单例Bean中正确使用原型Bean?
A: 使用ApplicationContext.getBean()、ObjectFactory或@Lookup注解。
Q3: request作用域和ThreadLocal的区别?
A: request作用域由Spring管理生命周期,自动清理;ThreadLocal需要手动清理,容易内存泄漏。
Q4: 为什么Web作用域需要代理?
A: 因为单例Bean在容器启动时就创建,而request/session作用域的Bean还不存在,需要通过代理延迟获取。