Appearance
Bean 作用域
Bean 作用域决定了Spring 容器创建一个 Bean 还是多个,以及 Bean 的生命周期范围。
一、五种作用域
┌─────────────────────────────────────────────────────────────┐
│ singleton(默认) │
│ 一个容器只有一个实例,全局共享 │
│ 创建时机:容器启动时(非懒加载) │
│ 生命周期:与容器相同 │
│ 适用:无状态 Bean(Service、Dao、Controller) │
├─────────────────────────────────────────────────────────────┤
│ prototype │
│ 每次获取都创建新实例 │
│ 创建时机:每次 getBean() 时 │
│ 生命周期:Spring 创建后不再管理,需自行销毁 │
│ 适用:有状态 Bean(每个请求有独立的状态) │
├─────────────────────────────────────────────────────────────┤
│ request(仅 Web 环境) │
│ 每个 HTTP 请求一个实例 │
│ 适用:一个请求范围内的数据共享 │
├─────────────────────────────────────────────────────────────┤
│ session(仅 Web 环境) │
│ 每个 HTTP Session 一个实例 │
│ 适用:用户登录信息、购物车 │
├─────────────────────────────────────────────────────────────┤
│ application(仅 Web 环境) │
│ 每个 ServletContext 一个实例 │
│ 类似 singleton,但区分于 Spring 容器的 singleton │
└─────────────────────────────────────────────────────────────┘二、代码演示
java
// singleton(默认)
@Component
public class OrderService { } // 全容器只有一个实例
// prototype
@Component
@Scope("prototype")
public class OrderContext { } // 每次 getBean 都创建新实例
// 验证
@RestController
public class TestController {
@Autowired
private ApplicationContext ctx;
@GetMapping("/test-scope")
public String test() {
OrderService s1 = ctx.getBean(OrderService.class);
OrderService s2 = ctx.getBean(OrderService.class);
System.out.println(s1 == s2); // true → 同一个实例
OrderContext c1 = ctx.getBean(OrderContext.class);
OrderContext c2 = ctx.getBean(OrderContext.class);
System.out.println(c1 == c2); // false → 不同实例
return "ok";
}
}三、singleton 中注入 prototype
java
// 问题:singleton 中注入 prototype,prototype 也变成了 singleton
@Component
public class OrderService {
@Autowired
private OrderContext context; // context 只注入一次,之后不再变化
}
// 解决方式一:注入 ApplicationContext(不推荐,耦合 Spring API)
@Component
public class OrderService {
@Autowired
private ApplicationContext ctx;
public void process() {
OrderContext context = ctx.getBean(OrderContext.class); // 每次新实例
}
}
// 解决方式二:@Lookup(推荐)
@Component
public class OrderService {
public void process() {
OrderContext context = createContext();
}
@Lookup
public OrderContext createContext() {
return null; // Spring 通过 CGLIB 重写此方法,每次返回新实例
}
}
// 解决方式三:ObjectFactory(推荐)
@Component
public class OrderService {
@Autowired
private ObjectFactory<OrderContext> contextFactory;
public void process() {
OrderContext context = contextFactory.getObject(); // 每次新实例
}
}四、面试要点
Q1:singleton 和 prototype 的区别?
singleton 整个容器只有一个实例,prototype 每次获取都创建新实例。singleton 默认在容器启动时创建,生命周期由 Spring 管理;prototype 在每次 getBean 时创建,Spring 创建后不再管理销毁。
Q2:singleton Bean 是线程安全的吗?
singleton 本身不保证线程安全。如果 Bean 是无状态的(不保存共享数据),则是线程安全的(如 Service、Dao)。如果 Bean 有状态(如保存了成员变量),则需要自行保证线程安全。
Q3:singleton 中注入 prototype 会怎样?
prototype 只会注入一次,之后一直使用同一个实例,失去了 prototype 的意义。解决方案:使用 @Lookup 或 ObjectFactory 每次获取新实例。
