一、核心概念

1. Bean 的作用域(Scope)

Spring 容器中 Bean 的生命周期范围和可见性。Spring Boot 支持以下作用域:

作用域 说明 使用场景
singleton (默认) 每个 Spring IoC 容器中仅存在一个共享的 Bean 实例 大多数无状态组件(Service, Repository)
prototype 每次请求(getBean() 或注入)都创建一个新的 Bean 实例 有状态的、需要独立状态的对象
request 每个 HTTP 请求创建一个新实例,仅在 Web 环境有效 与单个 HTTP 请求相关的数据
session 每个 HTTP Session 创建一个新实例,仅在 Web 环境有效 用户会话数据
application 为整个 ServletContext 生命周期创建一个 Bean,仅在 Web 环境有效 应用级共享数据
websocket 每个 WebSocket 会话创建一个 Bean,仅在 WebSocket 环境有效 WebSocket 会话数据

2. Bean 的生命周期

指 Bean 从创建、初始化、使用到销毁的全过程。Spring 提供了多个扩展点:

  1. 实例化 (Instantiation)
  2. 属性赋值 (Populate Properties)
  3. 初始化前 (BeanPostProcessor.postProcessBeforeInitialization)
  4. 初始化 (InitializingBean.afterPropertiesSet / @PostConstruct / init-method)
  5. 初始化后 (BeanPostProcessor.postProcessAfterInitialization)
  6. 使用 (In Use)
  7. 销毁前 (DestructionAwareBeanPostProcessor.postProcessBeforeDestruction)
  8. 销毁 (DisposableBean.destroy / @PreDestroy / destroy-method)

二、操作步骤(非常详细)

步骤 1:定义不同作用域的 Bean

1.1 Singleton Bean (默认)

@Component // 默认 scope = singleton
public class UserService {
    private int callCount = 0;

    public void doSomething() {
        System.out.println("UserService called: " + ++callCount);
    }
}

1.2 Prototype Bean

@Component
@Scope("prototype") // 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TransactionContext {
    private String transactionId;
    private LocalDateTime createTime;

    public TransactionContext() {
        this.transactionId = UUID.randomUUID().toString();
        this.createTime = LocalDateTime.now();
        System.out.println("TransactionContext created: " + transactionId);
    }

    // getters and setters
}

1.3 Request Scope Bean

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    private String requestId;
    private Instant requestTime;

    public RequestContext() {
        this.requestId = UUID.randomUUID().toString();
        this.requestTime = Instant.now();
        System.out.println("RequestContext created for request: " + requestId);
    }

    // getters and setters
}

proxyMode = TARGET_CLASS:创建 CGLIB 代理,解决 singleton Bean 注入 request scope Bean 的问题。

1.4 Session Scope Bean

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
    private String userId;
    private Map<String, Object> attributes = new HashMap<>();

    // getters, setters, and methods
}

步骤 2:在控制器中使用不同作用域的 Bean

@RestController
@RequestMapping("/scope")
public class ScopeController {

    @Autowired
    private UserService userService; // Singleton

    @Autowired
    private ApplicationContext applicationContext; // 用于获取 Prototype Bean

    @Autowired
    private RequestContext requestContext; // Request Scoped (via proxy)

    @Autowired
    private UserSession userSession; // Session Scoped (via proxy)

    @GetMapping("/singleton")
    public String testSingleton() {
        userService.doSomething(); // 调用次数会累积
        return "UserService call count: " + getCallCountFromUserService();
    }

    @GetMapping("/prototype")
    public String testPrototype() {
        // 必须通过 ApplicationContext 获取新实例
        TransactionContext ctx1 = applicationContext.getBean(TransactionContext.class);
        TransactionContext ctx2 = applicationContext.getBean(TransactionContext.class);
        return "Ctx1 ID: " + ctx1.getTransactionId() + ", Ctx2 ID: " + ctx2.getTransactionId() + 
               ", Are they same? " + (ctx1 == ctx2);
    }

    @GetMapping("/request")
    public String testRequest() {
        return "Current Request ID: " + requestContext.getRequestId() + 
               ", Time: " + requestContext.getRequestTime();
    }

    @PostMapping("/session/user")
    public String setUserInSession(@RequestParam String userId) {
        userSession.setUserId(userId);
        return "User " + userId + " set in session.";
    }

    @GetMapping("/session/user")
    public String getUserFromSession() {
        return "Current User in Session: " + userSession.getUserId();
    }
}

步骤 3:定义 Bean 生命周期回调

3.1 使用 @PostConstruct@PreDestroy

@Component
public class DatabaseConnectionManager {

    @PostConstruct
    public void init() {
        System.out.println("DatabaseConnectionManager: Initializing connections...");
        // 建立数据库连接池等
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("DatabaseConnectionManager: Cleaning up connections...");
        // 关闭连接池
    }
}

3.2 实现 InitializingBeanDisposableBean

@Component
public class FileWatcherService implements InitializingBean, DisposableBean {

    private WatchService watchService;

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("FileWatcherService: Starting file watcher...");
        watchService = FileSystems.getDefault().newWatchService();
        // 开始监听文件
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("FileWatcherService: Stopping file watcher...");
        if (watchService != null) {
            watchService.close();
        }
    }
}

3.3 使用 XML 配置的 init-methoddestroy-method (较少用)

public class LegacyService {
    public void startUp() {
        System.out.println("LegacyService: Starting up...");
    }

    public void shutDown() {
        System.out.println("LegacyService: Shutting down...");
    }
}
<bean id="legacyService" class="com.example.LegacyService" 
      init-method="startUp" destroy-method="shutDown"/>

步骤 4:使用 BeanPostProcessor (高级)

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before Initialization: " + beanName);
        // 可以修改 bean 或返回代理
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After Initialization: " + beanName);
        return bean;
    }
}

三、常见错误与解决方案

错误 原因 解决方案
BeanCreationException: Scope 'request' is not active 在非 Web 请求上下文中访问 request scope Bean 确保在 Web 请求中访问,或使用 @Lazy 延迟加载
Singleton Bean 注入 Prototype Bean 失效 Singleton Bean 在初始化时只获取一次 Prototype 实例 使用 @Scope(proxyMode = ...) 或通过 ApplicationContext 获取
@PreDestroy 方法未执行 应用正常关闭时未调用 context.close() 确保 ApplicationContext 被正确关闭(如 SpringApplication.exit()
循环依赖导致初始化失败 A 依赖 B,B 依赖 A 使用 @Lazy@PostConstruct 中注入、或重构设计
InitializingBean.afterPropertiesSet 抛出异常 初始化逻辑失败 捕获并处理异常,或使用 @PostConstruct 提供更清晰的错误信息

四、注意事项

  1. prototype Bean 的销毁:Spring 容器不管理 prototype Bean 的销毁,需要客户端代码负责清理资源。
  2. 作用域代理 (proxyMode):当 singleton Bean 需要注入 request/session 等 Web 作用域 Bean 时,必须使用 proxyMode,否则注入的是代理对象创建时的那个实例。
  3. @PostConstruct / @PreDestroy 与 JSR-250:需要 jakarta.annotation-api 依赖(Spring Boot 通常已包含)。
  4. 生命周期方法执行顺序
    • @PostConstruct -> InitializingBean.afterPropertiesSet -> init-method
    • destroy-method -> DisposableBean.destroy -> @PreDestroy
  5. BeanPostProcessor 影响所有 Bean:谨慎实现,避免性能问题或意外行为。

五、使用技巧

  1. @Lazy 注解

    • 延迟单个 Bean 的初始化:@Component @Lazy
    • 延迟依赖注入:@Autowired @Lazy private ExpensiveService service;
    • 全局延迟:@SpringBootApplication @EnableLazyInitialization
  2. ObjectProvider:安全地获取可能不存在或需要按需获取的 Bean。

@Autowired
private ObjectProvider<OptionalFeature> optionalFeatureProvider;

public void useOptionalFeature() {
    OptionalFeature feature = optionalFeatureProvider.getIfAvailable();
    if (feature != null) {
        feature.doSomething();
    }
}
  1. @Lookup 方法注入:解决 singleton Bean 获取 prototype Bean 的另一种方式(较少用)。
@Component
public abstract class CommandManager {
    public void processCommand() {
        // 每次调用createCommand()都返回一个新的Command实例
        Command command = createCommand();
        command.execute();
    }

    @Lookup // Spring 会动态生成子类实现此方法
    protected abstract Command createCommand();
}
  1. @Scope@Configuration:在 @Configuration 类中使用 @Bean 定义时,@Scope 直接标注在方法上。
@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public MyPrototypeBean myPrototypeBean() {
        return new MyPrototypeBean();
    }
}

六、最佳实践

  1. 默认使用 singleton:绝大多数无状态服务、数据访问对象都应是单例。
  2. 谨慎使用 prototype:仅在对象有独立状态且状态不共享时使用,注意资源清理。
  3. Web 作用域明确request 用于请求上下文,session 用于用户会话数据,避免滥用。
  4. 优先使用 @PostConstruct / @PreDestroy:比实现接口更简洁,是首选方式。
  5. 避免在 @PostConstruct 中做耗时操作:会阻塞应用启动。考虑使用 @EventListener 监听 ContextRefreshedEvent
  6. 清晰的资源管理:对于需要清理的资源(文件句柄、网络连接),务必在 @PreDestroyDisposableBean.destroy 中释放。
  7. 使用 @Profile 隔离环境相关 Bean:避免在错误环境中创建 Bean。

七、性能优化

  1. 减少 prototype Bean 的创建:频繁创建和销毁对象有开销。考虑使用对象池(如 Commons Pool2)。
  2. 延迟初始化 (@Lazy):减少启动时间,仅在需要时初始化大型或耗时 Bean。
  3. 避免在 BeanPostProcessor 中执行重操作:它会影响所有 Bean 的创建。
  4. 合理使用作用域代理:CGLIB 代理有轻微性能开销,仅在必要时使用 proxyMode
  5. 监控 Bean 生命周期:利用日志或 APM 工具监控 @PostConstruct@PreDestroy 的执行时间和频率,识别瓶颈。

总结

掌握 Bean 的作用域是理解对象生命周期和依赖关系的基础,而掌握生命周期是实现资源管理、初始化逻辑和优雅关闭的关键。

快速掌握路径

  1. 动手实践:创建不同作用域的 Bean 并在控制器中测试。
  2. 观察日志:在 @PostConstruct@PreDestroy 中添加日志,观察启动和关闭过程。
  3. 使用 --debug:查看应用启动时的 Bean 创建报告。
  4. 阅读官方文档:深入理解 BeanPostProcessor 等高级特性。

理解并正确应用这些概念,是构建健壮、高效 Spring Boot 应用的基石。