Skip to content

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 每次获取新实例。