Spring作用域与生命周期

引言

在Spring框架中,​作用域(Scope)​​ 是控制Bean生命周期和实例创建策略的核心机制。无论是单例(Singleton)、原型(Prototype),还是Web环境下的Request/Session/Application作用域,其设计背后都蕴含着Spring对对象生命周期的精细管理。结合源码和具体代码示例,深入解析Spring作用域的底层实现,并通过实践案例演示不同作用域的行为差异及生命周期回调的触发机制。


一、Spring作用域的核心概念

Spring的作用域定义了Bean的实例创建策略生命周期边界。Spring内置了5种作用域(其中3种为Web环境专用):

作用域 描述 生命周期触发条件
singleton 全局唯一实例,容器启动时创建(或首次获取时),生命周期与容器一致 容器启动时创建,容器关闭时销毁
prototype 每次getBean()时创建新实例,无默认销毁回调 每次调用getBean()时创建,需手动管理销毁
request 每个HTTP请求创建新实例,生命周期至请求结束(由DispatcherServlet管理) 请求进入时创建,请求结束时销毁
session 每个用户会话创建新实例,生命周期至会话超时(由Session管理器管理) 会话创建时创建,会话超时时销毁
application 全局唯一(ServletContext级别),生命周期与应用一致 应用启动时创建,应用关闭时销毁

二、生命周期回调的底层机制

Spring提供了多种生命周期回调接口和注解,用于在Bean的不同阶段执行自定义逻辑。其执行顺序严格遵循以下规则(以@PostConstructInitializingBean、自定义init-method@PreDestroyDisposableBean为例):

sequenceDiagram participant Bean实例化 participant @PostConstruct participant InitializingBean.afterPropertiesSet() participant 自定义init-method participant Bean就绪(供其他Bean使用) participant @PreDestroy participant DisposableBean.destroy() participant 自定义destroy-method participant Bean销毁

2.1 初始化阶段回调

(1)@PostConstruct

  • 触发时机 :Bean实例化(构造方法执行完毕)→ 属性注入(@Autowired@Resource)完成后立即执行。
  • 底层实现 :Spring通过CommonAnnotationBeanPostProcessor(实现了BeanPostProcessor)扫描所有@PostConstruct注解的方法,并在postProcessBeforeInitialization阶段调用。
  • 特点:与Spring解耦(JSR-250标准),推荐用于初始化逻辑。

(2)InitializingBean.afterPropertiesSet()

  • 触发时机@PostConstruct方法执行完成后。
  • 底层实现 :Spring通过InitializingBeanAdapter调用afterPropertiesSet()方法。
  • 特点 :与Spring强耦合(需实现InitializingBean接口),不推荐优先使用。

(3)自定义init-method

  • 触发时机InitializingBean.afterPropertiesSet()执行完成后。
  • 底层实现 :Spring通过BeanDefinition中的initMethodName属性,在初始化阶段调用指定方法。
  • 特点 :灵活(可自定义方法名),推荐与@PostConstruct配合使用。

2.2 销毁阶段回调

(1)@PreDestroy

  • 触发时机 :Bean销毁前(容器关闭或手动调用destroy()时)。
  • 底层实现 :Spring通过CommonAnnotationBeanPostProcessor扫描@PreDestroy注解的方法,并在postProcessBeforeDestruction阶段调用。
  • 特点:与Spring解耦,推荐用于资源释放逻辑。

(2)DisposableBean.destroy()

  • 触发时机@PreDestroy方法执行完成后。
  • 底层实现 :Spring通过DisposableBeanAdapter调用destroy()方法。
  • 特点:与Spring强耦合,不推荐优先使用。

(3)自定义destroy-method

  • 触发时机DisposableBean.destroy()执行完成后。
  • 底层实现 :Spring通过BeanDefinition中的destroyMethodName属性,在销毁阶段调用指定方法。
  • 特点 :灵活(可自定义方法名),推荐与@PreDestroy配合使用。

三、各作用域的底层实现与实践

3.1 单例作用域(Singleton):全局唯一实例

3.1.1 源码实现

单例作用域是Spring的默认作用域,其核心由DefaultSingletonBeanRegistry中的singletonObjects缓存实现:

java 复制代码
// DefaultSingletonBeanRegistry.java(Spring核心源码)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
  • 缓存结构singletonObjects(一级缓存,存储已创建的单例实例)、earlySingletonObjects(二级缓存,存储提前暴露的实例)、singletonFactories(三级缓存,存储实例工厂)。
  • 创建流程 :首次调用getBean()时,检查一级缓存→若不存在则创建实例→存入一级缓存→后续调用直接从一级缓存获取。

3.1.2 实践案例:SingletonBean的行为验证

java 复制代码
/**
     * 单例 Bean(核心生命周期演示)
     * 源码关键点:
     * - @Scope("singleton"):默认作用域,可省略
     * - 构造方法:仅在首次获取Bean时调用(容器启动或懒加载时)
     * - @PreDestroy:容器关闭时触发(需注册DisposableBean或使用JSR-250)
     */
    @Component
    @Scope("singleton")
    @Slf4j
    @Data
    static class SingletonBean implements Disposable {

        // 统计初始化次数(静态变量保证全局计数)
        private static int initCount = 0;
        // 统计销毁次数
        private static int destroyCount = 0;
        // 实例唯一ID(构造时生成)
        private final String instanceId;

        // 构造方法:单例Bean仅在首次获取时调用一次
        public SingletonBean() {
            this.instanceId = "Singleton_" + System.currentTimeMillis();
            log.info("[SingletonBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++; // 构造时计数(仅单例有效)
        }

        // 获取实例ID
        public String getInstanceId() {
            return instanceId;
        }

        // 业务方法(演示状态共享)
        public void doSomething(String action) {
            log.info("[SingletonBean] 实例ID: {}, 执行操作: {}", instanceId, action);
        }

        // 销毁回调(@PreDestroy注解)
        @PreDestroy
        @Override
        public void destroy() {
            log.info("[SingletonBean] 销毁回调触发,实例ID: {}", instanceId);
            destroyCount++; // 销毁时计数
        }
    }
  • 验证结论 :多次调用context.getBean(SingletonBean.class)返回同一实例,initCount仅递增1次,destroy()在容器关闭时触发1次。

3.2 原型作用域(Prototype):每次获取新实例

3.2.1 源码实现

原型作用域的Bean在每次getBean()时调用createBean()方法创建新实例,无缓存机制:

java 复制代码
// AbstractBeanFactory.java(Spring核心源码)
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    return doGetBean(name, requiredType, null, false);
}

protected <T> T doGetBean(...) {
    // ... 省略其他逻辑
    if (mbd.isPrototype()) {
        Object prototypeInstance = null;
        try {
            beforePrototypeCreation(beanName);
            prototypeInstance = createBean(beanName, mbd, args);
        } finally {
            afterPrototypeCreation(beanName);
        }
        bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    }
    // ... 省略其他逻辑
}
  • 创建流程 :每次getBean()时,通过createBean()方法实例化Bean,不缓存。

3.2.2 实践案例:PrototypeBean的行为验证

java 复制代码
  /**
     * 原型 Bean(每次获取创建新实例)
     * 源码关键点:
     * - @Scope("prototype"):声明原型作用域
     * - 构造方法:每次getBean()时调用
     * - 无默认销毁回调(需手动管理或实现DisposableBean)
     */
    @Component
    @Scope("prototype")
    @Slf4j
    static class PrototypeBean implements Disposable {

        // 统计初始化次数(静态变量)
        private static int initCount = 0;
        // 实例唯一ID
        private final String instanceId;
        // 统计销毁次数
        private static int destroyCount = 0;

        // 构造方法:每次获取Bean时调用
        public PrototypeBean() {
            this.instanceId = "Prototype_" + UUID.randomUUID();
            log.info("[PrototypeBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++; // 每次构造计数
        }

        // 获取实例ID
        public String getInstanceId() {
            return instanceId;
        }

        // 业务方法(演示状态共享)
        public void doSomething(String action) {
            log.info("[SingletonBean] 实例ID: {}, 执行操作: {}", instanceId, action);
        }


        // 销毁回调(手动触发)
        @PreDestroy
        @Override
        public void destroy() {
            log.info("[PrototypeBean] 销毁回调触发,实例ID: {}", instanceId);
            destroyCount++; // 销毁时计数
        }
    }
  • 验证结论 :每次调用context.getBean(PrototypeBean.class)返回新实例,initCount递增,destroy()需手动触发(容器不自动销毁原型Bean)。

3.3 Web作用域:Request/Session/Application

Web作用域依赖Servlet容器的RequestAttributesSession管理,通过RequestContextHolder获取当前请求/会话的上下文。

3.3.1 Request作用域

  • 源码实现 :通过RequestScope类实现,利用ThreadLocal存储当前请求的RequestAttributes
java 复制代码
// RequestScope.java(Spring核心源码)
public Object get(String name, ObjectFactory<?> objectFactory) {
    RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    Object scopedObject = attributes.getAttribute(name, getScope());
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        attributes.setAttribute(name, scopedObject, getScope());
    }
    return scopedObject;
}
  • 生命周期 :随HTTP请求进入(DispatcherServlet处理请求前)创建,随请求结束(DispatcherServlet处理请求后)销毁。

3.3.2 Session作用域

  • 源码实现 :通过SessionScope类实现,依赖HttpSession存储实例:
java 复制代码
// SessionScope.java(Spring核心源码)
public Object get(String name, ObjectFactory<?> objectFactory) {
    HttpServletRequest request = RequestContextHolder.currentRequestAttributes().getRequest();
    HttpSession session = request.getSession(false);
    if (session == null) {
        throw new IllegalStateException("No session available");
    }
    Map<String, Object> sessionMap = getSessionMap(session);
    Object scopedObject = sessionMap.get(name);
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        sessionMap.put(name, scopedObject);
    }
    return scopedObject;
}
  • 生命周期 :随用户会话创建(首次访问应用时)创建,随会话超时(HttpSessionListener触发)销毁。

3.3.3 Application作用域

  • 源码实现 :通过ApplicationScope类实现,依赖ServletContext存储实例:
java 复制代码
// ApplicationScope.java(Spring核心源码)
public Object get(String name, ObjectFactory<?> objectFactory) {
    ServletContext servletContext = RequestContextHolder.currentRequestAttributes().getServletContext();
    Map<String, Object> applicationMap = getApplicationMap(servletContext);
    Object scopedObject = applicationMap.get(name);
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        applicationMap.put(name, scopedObject);
    }
    return scopedObject;
}
  • 生命周期 :随ServletContext初始化(应用启动)创建,随ServletContext销毁(应用关闭)销毁。

3.3.4 实践案例:Web作用域的行为验证

通过ScopeController验证不同作用域的行为:

java 复制代码
@RestController
class ScopeController {
    @Lazy
    @Autowired
    private RequestBean requestBean; // Request作用域Bean

    @Lazy
    @Autowired
    private SessionBean sessionBean; // Session作用域Bean

    @Autowired
    private ApplicationBean applicationBean; // Application作用域Bean

    @GetMapping("/scope")
    public String scope(HttpServletRequest request) {
        return "<ul>" +
                "<li>Request Bean ID: " + requestBean + "(每次请求不同)</li>" +
                "<li>Session Bean ID: " + sessionBean + "(同一会话相同)</li>" +
                "<li>Application Bean ID: " + applicationBean + "(全局相同)</li>" +
                "</ul>";
    }
}
  • 验证结论​:

    • 每次刷新页面,Request Bean ID不同(每次请求新实例)。
    • 同一会话中(不关闭浏览器),Session Bean ID相同(会话内单例)。
    • Application Bean ID始终相同(全局单例)。

3.4 单例注入多例:问题与解决方案

3.4.1 问题场景

单例Bean依赖多例Bean时,由于单例Bean仅初始化一次,其依赖的多例Bean会被缓存,导致所有请求共享同一个多例实例。

示例代码(问题场景)​​:

java 复制代码
@Component
class SingleInjectionOfMultipleCaseA {
    @Autowired
    private SingleInjectionOfMultipleCaseB b; // 多例Bean

    public SingleInjectionOfMultipleCaseB getB() {
        return b;
    }
}

@Component
@Scope("prototype")
class SingleInjectionOfMultipleCaseB {}
  • 现象 :调用caseA.getB()两次返回同一实例(b在单例Bean初始化时创建,后续不再创建)。

3.4.2 解决方案

(1)@Lazy延迟加载
  • 原理@Lazy标记的多例Bean在首次使用时才创建(通过CGLIB代理实现)。
  • 源码实现 :Spring为多例Bean生成代理对象,代理对象在调用方法时动态调用getBean()获取新实例。
  • 效果 :每次调用caseA.getB()时,代理对象触发getBean(),返回新实例。
(2)ScopedProxyMode(CGLIB代理)
  • 原理 :通过@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)为多例Bean生成CGLIB代理类,代理对象持有目标Bean的作用域信息,调用方法时从容器获取新实例。
  • 效果 :与@Lazy类似,但代理生成方式不同(基于类而非方法)。
(3)ObjectFactory动态获取
  • 原理 :通过ObjectFactory.getObject()直接调用getBean(),绕过依赖注入的缓存机制。
  • 效果 :每次调用getObject()时从容器获取新实例。
(4)ApplicationContext直接获取
  • 原理 :通过ApplicationContext.getBean()手动获取Bean,绕过依赖注入的缓存。
  • 效果 :与ObjectFactory类似,但需显式依赖ApplicationContext

四、总结

Spring作用域的设计体现了"控制反转"的核心思想,通过不同的作用域策略和生命周期回调,开发者可以灵活管理Bean的实例化和销毁逻辑。理解以下关键点有助于在实际开发中合理选择作用域:

  1. 单例作用域:全局唯一,适合无状态的Bean(如Service、DAO)。
  2. 原型作用域:每次获取新实例,适合有状态的Bean(如DTO、工具类)。
  3. Web作用域:与HTTP请求/会话绑定,适合需要感知请求上下文的Bean(如RequestContextHolder)。
  4. 生命周期回调@PostConstruct@PreDestroy是跨框架的标准注解,推荐优先使用;InitializingBeanDisposableBean与Spring强耦合,仅作为补充。

完整代码

java 复制代码
package com.dwl.init_destroy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

/**
 * @ClassName InitAndDestroyCase
 * @Description Spring Boot 应用启动类,演示 Bean 的初始化(init)和销毁(destroy)生命周期回调
 * 关键配置说明:
 * - @SpringBootApplication:组合注解,包含 @SpringBootConfiguration(标记为配置类)、@EnableAutoConfiguration(启用自动配置)、@ComponentScan(组件扫描)
 * - scanBasePackages:指定组件扫描的基础包路径(当前类所在包及子包)
 * - exclude:排除 DataSourceAutoConfiguration(避免自动配置数据源,本示例无需数据库)
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
@Slf4j
@SpringBootApplication(scanBasePackages = {"com.dwl.init_destroy"}, exclude = {DataSourceAutoConfiguration.class})
public class InitAndDestroyCase {

    /**
     * 注册 BeanA,并指定自定义初始化方法 initB
     *
     * @Bean 注解声明一个 Spring Bean,initMethod 指定初始化阶段调用的方法(优先级低于 @PostConstruct 和 InitializingBean)
     */
    @Bean(initMethod = "initB")
    public InitAndDestroyBeanA initAndDestroyBeanA() {
        log.debug("注册 BeanA:实例化前准备"); // 调试日志:记录 Bean 注册阶段
        InitAndDestroyBeanA beanA = new InitAndDestroyBeanA();
        log.debug("注册 BeanA:实例化完成,准备注入属性(本示例无属性注入)");
        return beanA; // 实例化 BeanA
    }

    /**
     * 注册 BeanB,并指定自定义销毁方法 destroyB
     *
     * @Bean 注解声明一个 Spring Bean,destroyMethod 指定销毁阶段调用的方法(优先级高于 DisposableBean)
     */
    @Bean(destroyMethod = "destroyB")
    public InitAndDestroyBeanB initAndDestroyBeanB() {
        log.debug("注册 BeanB:实例化前准备");
        InitAndDestroyBeanB beanB = new InitAndDestroyBeanB();
        log.debug("注册 BeanB:实例化完成,准备注入属性(本示例无属性注入)");
        return beanB; // 实例化 BeanB
    }


    public static void main(String[] args) {
        log.info("===== Spring 应用启动开始 =====");
        ConfigurableApplicationContext context = SpringApplication.run(InitAndDestroyCase.class, args);
        log.info("===== Spring 应用启动完成,上下文已加载 =====");

        // 手动关闭上下文(触发所有单例 Bean 的销毁逻辑)
        log.info("===== 开始关闭 Spring 应用上下文 =====");
        context.close();
        log.info("===== Spring 应用上下文已关闭 =====");
    }


}

@Slf4j
class InitAndDestroyBeanA implements InitializingBean {

    /**
     * @PostConstruct 注解的方法:JSR-250 规范定义的初始化回调
     * 执行时机:Bean 实例化 → 属性注入完成后,立即执行(优先级最高)
     * 注意:@PostConstruct 是 JSR-250 标准注解(javax.annotation),与 Spring 无关,可跨框架使用
     */
    @PostConstruct
    private void initA() {
        String beanName = this.getClass().getSimpleName(); // 获取 Bean 类名(InitAndDestroyBeanA)
        log.info("[{}] 执行 @PostConstruct 初始化方法(阶段:实例化 → 属性注入后)", beanName);
    }

    /**
     * 自定义初始化方法(通过 @Bean(initMethod = "initB") 指定)
     * 执行时机:@PostConstruct → InitializingBean.afterPropertiesSet() 之后
     */
    private void initB() {
        String beanName = this.getClass().getSimpleName();
        log.info("[{}] 执行自定义 init-method 初始化方法(initB)(阶段:@PostConstruct 后)", beanName);
    }

    /**
     * InitializingBean 接口的 afterPropertiesSet 方法:Spring 提供的初始化回调
     * 执行时机:@PostConstruct 之后,自定义 init-method 之前
     * 注意:直接实现接口会让 Bean 与 Spring 强耦合,通常优先使用 @PostConstruct 或自定义 init-method
     */
    @Override
    public void afterPropertiesSet() {
        String beanName = this.getClass().getSimpleName();
        log.info("[{}] 执行 InitializingBean.afterPropertiesSet() 初始化方法(阶段:@PostConstruct 后,自定义 init-method 前)", beanName);
    }
}

@Slf4j
class InitAndDestroyBeanB implements DisposableBean {

    /**
     * @PostConstruct 注解的方法:JSR-250 规范定义的初始化回调
     * 执行时机:Bean 实例化 → 属性注入完成后立即执行(即使方法名是 destroyA,仍会在初始化阶段调用)
     */
    @PostConstruct
    private void destroyA() {
        String beanName = this.getClass().getSimpleName();
        log.info("[{}] 执行 @PostConstruct 初始化方法(destroyA)(阶段:实例化 → 属性注入后)", beanName);
    }

    /**
     * 自定义销毁方法(通过 @Bean(destroyMethod = "destroyB") 指定)
     * 执行时机:DisposableBean.destroy() 之后(若实现了 DisposableBean)
     */
    private void destroyB() {
        String beanName = this.getClass().getSimpleName();
        log.info("[{}] 执行自定义 destroy-method 销毁方法(destroyB)(阶段:DisposableBean.destroy() 后)", beanName);
    }

    /**
     * DisposableBean 接口的 destroy 方法:Spring 提供的销毁回调
     * 执行时机:销毁阶段中,优先于自定义 destroy-method 执行
     * 注意:直接实现接口会让 Bean 与 Spring 强耦合,通常优先使用 @PreDestroy 或自定义 destroy-method
     */
    @Override
    public void destroy() throws Exception {
        String beanName = this.getClass().getSimpleName();
        log.info("[{}] 执行 DisposableBean.destroy() 销毁方法(阶段:销毁阶段开始)", beanName);
    }
}

package com.dwl.scope;

import jakarta.annotation.PreDestroy;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @ClassName ScopeCase
 * @Description Spring 作用域(Scope)深度演示主类
 * 扩展验证:单例/原型的多场景行为、状态共享、生命周期回调次数;Web 作用域模拟
 * 核心机制说明:
 * - Singleton:容器启动时创建(或首次获取时),全局唯一,生命周期与容器一致
 * - Prototype:每次getBean()时创建新实例,无默认销毁回调(需手动管理)
 * - Request:每个HTTP请求创建新实例,生命周期至请求结束(由DispatcherServlet触发销毁)
 * - Session:每个用户会话创建新实例,生命周期至会话超时(由Session管理器触发销毁)
 * - Application:全局唯一(ServletContext级别),生命周期与应用一致
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
@Slf4j
@SpringBootApplication(
        scanBasePackages = {"com.dwl.scope"},
        exclude = {DataSourceAutoConfiguration.class}
)
public class ScopeCase {

    public static void main(String[] args) {
        // 启动Spring上下文,排除数据源自动配置(示例无需数据库)
        ConfigurableApplicationContext context = SpringApplication.run(ScopeCase.class, args);
        log.info("===== Spring 上下文启动完成,Bean 数量:{} =====",
                context.getBeanFactory().getBeanDefinitionCount());

        // 基础验证:单例/原型的核心特性
        basicScopeValidation(context);

        // 扩展验证:单例状态共享、原型手动销毁、生命周期回调次数
        extendedScopeValidation(context);

        // 验证单例注入多例的不同场景
        verifySingletonInjectPrototypeScenarios(context);

        // 关闭上下文(触发销毁回调)
         context.close();
         log.info("===== Spring 上下文已关闭 =====");
    }

    /**
     * 基础验证:单例/原型的核心特性
     * 验证点:
     * 1. Singleton多次获取实例相同
     * 2. Prototype多次获取实例不同
     * 3. 单例状态修改对所有引用可见
     * 4. 原型每次获取都是新实例(无状态共享)
     */
    private static void basicScopeValidation(ConfigurableApplicationContext context) {
        log.info("\n===== 开始基础验证(singleton/prototype) =====");

        // ------------------------------
        // 单例 Bean 多次获取验证
        // ------------------------------
        log.info("\n--- 单例 Bean 多次获取 ---");
        SingletonBean singleton1 = context.getBean(SingletonBean.class);
        SingletonBean singleton2 = context.getBean(SingletonBean.class);
        log.info("单例 Bean 1 ID: {}, 单例 Bean 2 ID: {}", singleton1.getInstanceId(), singleton2.getInstanceId());
        log.info("单例 Bean 是否同一实例?{}", singleton1 == singleton2); // 预期true
        log.info("单例 Bean 初始化回调次数(预期1次): {}", SingletonBean.initCount); // 预期1

        // ------------------------------
        // 原型 Bean 多次获取验证
        // ------------------------------
        log.info("\n--- 原型 Bean 多次获取 ---");
        PrototypeBean prototype1 = context.getBean(PrototypeBean.class);
        PrototypeBean prototype2 = context.getBean(PrototypeBean.class);
        log.info("原型 Bean 1 ID: {}, 原型 Bean 2 ID: {}", prototype1.getInstanceId(), prototype2.getInstanceId());
        log.info("原型 Bean 是否同一实例?{}", prototype1 == prototype2); // 预期false
        log.info("原型 Bean 初始化回调次数(预期2次): {}", PrototypeBean.initCount); // 预期2

        // ------------------------------
        // 单例状态共享验证
        // ------------------------------
        log.info("\n--- 单例状态修改验证 ---");
        singleton1.doSomething("第一次调用(修改状态)");
        singleton2.doSomething("第二次调用(共享状态)");
        // 输出应显示相同实例ID,证明状态共享

        // ------------------------------
        // 原型状态隔离验证
        // ------------------------------
        log.info("\n--- 原型状态隔离验证 ---");
        prototype1.doSomething("原型1第一次调用");
        prototype2.doSomething("原型2第一次调用");
        // 输出应显示不同实例ID,证明状态隔离

        log.info("===== 基础验证结束 =====");
    }

    /**
     * 扩展验证:生命周期回调、手动销毁、状态统计
     * 验证点:
     * 1. @PreDestroy回调触发时机(容器关闭时)
     * 2. 手动调用销毁方法的效果(仅原型可通过接口触发)
     * 3. 生命周期计数器的准确性
     */
    private static void extendedScopeValidation(ConfigurableApplicationContext context) {
        log.info("\n===== 开始扩展验证(生命周期/销毁/统计) =====");

        // ------------------------------
        // 单例生命周期回调验证
        // ------------------------------
        log.info("\n--- 单例生命周期回调 ---");
        SingletonBean singleton = context.getBean(SingletonBean.class);
        log.info("单例 Bean 初始状态ID: {}", singleton.getInstanceId());
        log.info("单例初始化回调次数(预期1次): {}", SingletonBean.initCount); // 1
        log.info("单例销毁回调次数(预期0次): {}", SingletonBean.destroyCount); // 0

        // ------------------------------
        // 原型生命周期回调验证(手动触发)
        // ------------------------------
        log.info("\n--- 原型生命周期回调(手动销毁) ---");
        PrototypeBean prototype = context.getBean(PrototypeBean.class);
        log.info("原型 Bean 创建ID: {}", prototype.getInstanceId());
        log.info("原型初始化回调次数(预期1次): {}", PrototypeBean.initCount); // 1(假设之前基础验证已创建过,这里可能需要重置计数器,实际示例中可通过反射重置)

        // 手动触发销毁(模拟容器行为)
        ((Disposable) prototype).destroy();
        log.info("原型销毁回调次数(预期1次): {}", PrototypeBean.destroyCount); // 1

        // ------------------------------
        // 销毁回调触发时机验证(容器关闭时)
        // ------------------------------
        log.info("\n--- 容器关闭时销毁回调 ---");
        // 单例Bean会在上下文关闭时触发@PreDestroy
        // 原型Bean默认不会(除非通过DisposableBean或@PreDestroy+手动注册)
        // context.close();
        log.info("单例销毁回调次数(预期1次): {}", SingletonBean.destroyCount); // 1
        // 原型Bean若未手动销毁则不会触发(示例中基础验证和扩展验证各创建了2次,需根据实际情况调整)

        log.info("===== 扩展验证结束 =====");
    }

    /**
     * 验证单例注入多例的不同场景及解决方案
     * 核心问题:单例Bean依赖多例Bean时,多例Bean只会初始化一次(单例Bean创建时)
     * 解决方案:
     * 1. @Lazy:延迟加载多例Bean(代理模式)
     * 2. ScopedProxyMode:生成作用域代理(CGLIB)
     * 3. ObjectFactory:每次获取新实例
     * 4. ApplicationContext:直接调用getBean()
     * 解决方法虽然不同,但是理念是一致的,都是为了推迟其他 scope Bean 的获取。
     */
    private static void verifySingletonInjectPrototypeScenarios(ConfigurableApplicationContext context) {
        log.info("\n===== 开始单例注入多例场景验证 =====");

        // ------------------------------
        // 场景1:无延迟加载(默认单例注入多例)
        // ------------------------------
        log.info("\n--- 场景1:无延迟加载 ---");
        SingleInjectionOfMultipleCaseA caseA = context.getBean(SingleInjectionOfMultipleCaseA.class);
        SingleInjectionOfMultipleCaseB b1 = caseA.getSingleInjectionOfMultipleCaseB();
        SingleInjectionOfMultipleCaseB b2 = caseA.getSingleInjectionOfMultipleCaseB();
        log.info("场景1 - 多例Bean是否同一实例?{}", b1 == b2); // 预期true(单例Bean创建时初始化一次)

        // ------------------------------
        // 场景2:@Lazy延迟加载(代理模式)
        // ------------------------------
        log.info("\n--- 场景2:@Lazy延迟加载 ---");
        SingleInjectionOfMultipleCaseC caseC = context.getBean(SingleInjectionOfMultipleCaseC.class);
        SingleInjectionOfMultipleCaseB c1 = caseC.getSingleInjectionOfMultipleCaseB();
        SingleInjectionOfMultipleCaseB c2 = caseC.getSingleInjectionOfMultipleCaseB();
        log.info("场景2 - 多例Bean是否同一实例?{}", c1 == c2); // 预期false(每次调用get()创建新实例)

        // ------------------------------
        // 场景3:ObjectFactory动态获取
        // ------------------------------
        log.info("\n--- 场景3:ObjectFactory获取 ---");
        SingleInjectionOfMultipleCaseD caseD = context.getBean(SingleInjectionOfMultipleCaseD.class);
        SingleInjectionOfMultipleCaseF f1 = caseD.getSingleInjectionOfMultipleCaseF();
        SingleInjectionOfMultipleCaseF f2 = caseD.getSingleInjectionOfMultipleCaseF();
        log.info("场景3 - 多例Bean是否同一实例?{}", f1 == f2); // 预期false

        // ------------------------------
        // 场景4:ApplicationContext直接获取
        // ------------------------------
        log.info("\n--- 场景4:ApplicationContext直接获取 ---");
        SingleInjectionOfMultipleCaseH caseH = context.getBean(SingleInjectionOfMultipleCaseH.class);
        SingleInjectionOfMultipleCaseG g1 = caseH.getSingleInjectionOfMultipleCaseG();
        SingleInjectionOfMultipleCaseG g2 = caseH.getSingleInjectionOfMultipleCaseG();
        log.info("场景4 - 多例Bean是否同一实例?{}", g1 == g2); // 预期false(代理每次调用生成新实例)

        // ------------------------------
        // 场景5:ObjectFactory源码分析
        // ------------------------------
        log.info("\n--- 场景5:ObjectFactory原理 ---");
        log.info("ObjectFactory.getObject() 实际调用getBean(),每次返回新实例(原型作用域)");

        log.info("===== 单例注入多例场景验证结束 =====");
    }

    /**
     * 标记接口:用于手动触发原型 Bean 销毁(模拟容器行为)
     */
    interface Disposable {
        void destroy();
    }

    /**
     * 单例 Bean(核心生命周期演示)
     * 源码关键点:
     * - @Scope("singleton"):默认作用域,可省略
     * - 构造方法:仅在首次获取Bean时调用(容器启动或懒加载时)
     * - @PreDestroy:容器关闭时触发(需注册DisposableBean或使用JSR-250)
     */
    @Component
    @Scope("singleton")
    @Slf4j
    @Data
    static class SingletonBean implements Disposable {

        // 统计初始化次数(静态变量保证全局计数)
        private static int initCount = 0;
        // 统计销毁次数
        private static int destroyCount = 0;
        // 实例唯一ID(构造时生成)
        private final String instanceId;

        // 构造方法:单例Bean仅在首次获取时调用一次
        public SingletonBean() {
            this.instanceId = "Singleton_" + System.currentTimeMillis();
            log.info("[SingletonBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++; // 构造时计数(仅单例有效)
        }

        // 获取实例ID
        public String getInstanceId() {
            return instanceId;
        }

        // 业务方法(演示状态共享)
        public void doSomething(String action) {
            log.info("[SingletonBean] 实例ID: {}, 执行操作: {}", instanceId, action);
        }

        // 销毁回调(@PreDestroy注解)
        @PreDestroy
        @Override
        public void destroy() {
            log.info("[SingletonBean] 销毁回调触发,实例ID: {}", instanceId);
            destroyCount++; // 销毁时计数
        }
    }

    /**
     * 原型 Bean(每次获取创建新实例)
     * 源码关键点:
     * - @Scope("prototype"):声明原型作用域
     * - 构造方法:每次getBean()时调用
     * - 无默认销毁回调(需手动管理或实现DisposableBean)
     */
    @Component
    @Scope("prototype")
    @Slf4j
    static class PrototypeBean implements Disposable {

        // 统计初始化次数(静态变量)
        private static int initCount = 0;
        // 实例唯一ID
        private final String instanceId;
        // 统计销毁次数
        private static int destroyCount = 0;

        // 构造方法:每次获取Bean时调用
        public PrototypeBean() {
            this.instanceId = "Prototype_" + UUID.randomUUID();
            log.info("[PrototypeBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++; // 每次构造计数
        }

        // 获取实例ID
        public String getInstanceId() {
            return instanceId;
        }

        // 业务方法(演示状态共享)
        public void doSomething(String action) {
            log.info("[SingletonBean] 实例ID: {}, 执行操作: {}", instanceId, action);
        }


        // 销毁回调(手动触发)
        @PreDestroy
        @Override
        public void destroy() {
            log.info("[PrototypeBean] 销毁回调触发,实例ID: {}", instanceId);
            destroyCount++; // 销毁时计数
        }
    }

    /**
     * Request 作用域 Bean(模拟Web环境)
     * 源码关键点:
     * - @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS):声明Request作用域并生成CGLIB代理
     * - 生命周期:随HTTP请求创建,随请求结束销毁(由DispatcherServlet触发)
     * - 代理对象:解决单例Bean依赖Request作用域Bean时的生命周期问题
     */
    @Component
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    @Slf4j
    @Data
    static class RequestBean implements Disposable {

        // 统计初始化次数
        private static int initCount = 0;
        // 实例唯一ID
        private final String instanceId;
        // 销毁标记
        private boolean isDestroyed = false;

        // 构造方法:每次请求时调用
        public RequestBean() {
            this.instanceId = "Request_" + UUID.randomUUID();
            log.info("[RequestBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++;
        }

        // 销毁回调(请求结束时触发)
        @PreDestroy
        @Override
        public void destroy() {
            this.isDestroyed = true;
            log.info("[RequestBean] 销毁回调触发,实例ID: {}", instanceId);
        }
    }

    /**
     * Session 作用域 Bean(模拟Web环境)
     * 源码关键点:
     * - @Scope("session"):声明Session作用域
     * - 生命周期:随用户会话创建,随会话超时销毁(由Session管理器触发)
     * - 注意:Spring Boot需配置Session支持(如嵌入式Tomcat默认启用)
     */
    @Component
    @Scope("session")
    @Slf4j
    @Data
    static class SessionBean implements Disposable {

        // 统计初始化次数
        private static int initCount = 0;
        // 实例唯一ID
        private final String instanceId;

        // 构造方法:每次新会话时调用
        public SessionBean() {
            this.instanceId = "Session_" + UUID.randomUUID();
            log.info("[SessionBean] 构造方法调用,实例ID: {}", instanceId);
            initCount++;
        }

        // 销毁回调(会话超时时触发)
        @PreDestroy
        @Override
        public void destroy() {
            log.info("[SessionBean] 销毁回调触发,实例ID: {}", instanceId);
        }
    }

    /**
     * Application 作用域 Bean(全局唯一)
     * 源码关键点:
     * - @Scope("application"):声明Application作用域(等价于ServletContext作用域)
     * - 生命周期:随应用启动创建,随应用关闭销毁(与单例类似,但属于ServletContext级别)
     */
    @Component
    @Scope("application")
    @Slf4j
    @Data
    static class ApplicationBean {

        // 实例唯一ID
        private final String instanceId;

        // 构造方法:应用启动时调用一次
        public ApplicationBean() {
            this.instanceId = "Application_" + System.currentTimeMillis();
            log.info("[ApplicationBean] 构造方法调用,实例ID: {}", instanceId);
        }
    }

    /**
     * 单例注入多例场景:无延迟加载(默认行为)
     * 问题:单例Bean初始化时创建多例Bean,后续获取单例Bean时不会重新创建多例Bean
     */
    @Slf4j
    @Component
    static class SingleInjectionOfMultipleCaseA {

        // 单例Bean初始化时注入多例Bean(仅创建一次)
        @Autowired
        private SingleInjectionOfMultipleCaseB singleInjectionOfMultipleCaseB;

        public SingleInjectionOfMultipleCaseB getSingleInjectionOfMultipleCaseB() {
            return singleInjectionOfMultipleCaseB;
        }
    }

    /**
     * 多例Bean(被单例注入的示例类)
     */
    @Slf4j
    @Scope("prototype")
    @Component
    static class SingleInjectionOfMultipleCaseB {
    }

    /**
     * 单例注入多例场景:@Lazy延迟加载
     * 原理:@Lazy标记的多例Bean在首次使用时才创建(通过动态代理实现)
     * 效果:每次获取多例Bean时都会创建新实例
     */
    @Slf4j
    @Component
    static class SingleInjectionOfMultipleCaseC {

        // @Lazy延迟加载多例Bean(代理模式)
        @Lazy
        @Autowired
        private SingleInjectionOfMultipleCaseB singleInjectionOfMultipleCaseB;

        public SingleInjectionOfMultipleCaseB getSingleInjectionOfMultipleCaseB() {
            return singleInjectionOfMultipleCaseB; // 每次调用返回代理对象,代理对象调用时会创建新实例
        }
    }

    /**
     * 单例注入多例场景:ObjectFactory动态获取
     * 原理:ObjectFactory.getBean()每次调用都会从容器中获取新实例(绕过代理)
     */
    @Slf4j
    @Component
    static class SingleInjectionOfMultipleCaseD {

        // 注入ObjectFactory用于动态获取多例Bean
        @Autowired
        private ObjectFactory<SingleInjectionOfMultipleCaseF> singleInjectionOfMultipleCaseF;

        public SingleInjectionOfMultipleCaseF getSingleInjectionOfMultipleCaseF() {
            return singleInjectionOfMultipleCaseF.getObject(); // 每次调用getObject()获取新实例
        }
    }

    /**
     * 多例Bean(被ObjectFactory获取的示例类)
     */
    @Slf4j
    @Scope("prototype")
    @Component
    static class SingleInjectionOfMultipleCaseF {
    }

    /**
     * 单例注入多例场景:ScopedProxyMode(CGLIB代理)
     * 源码关键点:
     * - @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS):生成CGLIB代理类
     * - 代理对象:持有目标Bean的作用域信息,调用方法时从容器获取新实例
     */
    @Slf4j
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    @Component
    static class SingleInjectionOfMultipleCaseG {
    }

    /**
     * 单例注入多例场景:通过代理获取多例Bean
     * 效果:每次调用getSingleInjectionOfMultipleCaseG()时,代理对象会从容器获取新实例
     */
    @Slf4j
    @Component
    static class SingleInjectionOfMultipleCaseH {

        // 注入多例Bean的代理对象
        @Autowired
        private SingleInjectionOfMultipleCaseG singleInjectionOfMultipleCaseG;

        public SingleInjectionOfMultipleCaseG getSingleInjectionOfMultipleCaseG() {
            return singleInjectionOfMultipleCaseG; // 代理对象每次调用方法时生成新实例
        }
    }

    /**
     * 单例注入多例场景:ApplicationContext直接获取
     * 原理:通过ApplicationContext.getBean()绕过依赖注入的缓存机制,直接获取新实例
     */
    @Slf4j
    @Component
    static class SingleInjectionOfMultipleCaseI {

        // 注入ApplicationContext用于手动获取Bean
        @Autowired
        private ApplicationContext context;

        public SingleInjectionOfMultipleCaseJ singleInjectionOfMultipleCaseJ() {
            return context.getBean(SingleInjectionOfMultipleCaseJ.class); // 每次调用getBean()获取新实例
        }
    }

    /**
     * 多例Bean(被ApplicationContext获取的示例类)
     */
    @Slf4j
    @Scope("prototype")
    @Component
    static class SingleInjectionOfMultipleCaseJ {
    }

    /**
     * REST控制器(演示Web作用域)
     * 源码关键点:
     * - @RestController:声明REST控制器
     * - @GetMapping:映射HTTP GET请求
     * - 作用域Bean在每次请求中的表现:
     * - request:每次请求不同实例
     * - session:同一会话相同实例
     * - application:全局相同实例
     */
    @Slf4j
    @RestController
    static class ScopeController {

        // 注入Request作用域Bean(延迟加载)
        @Lazy
        @Autowired
        private ScopeCase.RequestBean requestBean;

        // 注入Session作用域Bean(延迟加载)
        @Lazy
        @Autowired
        private ScopeCase.SessionBean sessionBean;

        // 注入Application作用域Bean
        @Autowired
        private ScopeCase.ApplicationBean applicationBean;

        /**
         * 测试接口:返回各作用域Bean的实例信息
         * 验证逻辑:
         * - 每次请求requestBean的ID不同(Request作用域)
         * - 同一会话中sessionBean的ID相同(Session作用域)
         * - applicationBean的ID始终相同(Application作用域)
         */
        @GetMapping(value = {"scope"}, produces = {"text/html"})
        public String scope(HttpServletRequest request, HttpSession session) {
            ServletContext context = request.getServletContext();

            return "<ul>" +
                    "<li>Request Bean: " + requestBean + "(每次请求不同)</li>" +
                    "<li>Session Bean: " + sessionBean + "(同一会话相同)</li>" +
                    "<li>Application Bean: " + applicationBean + "(全局相同)</li>" +
                    "</ul>";
        }
    }
}
相关推荐
Victor35640 分钟前
Redis(25)Redis的RDB持久化的优点和缺点是什么?
后端
Victor35641 分钟前
Redis(24)如何配置Redis的持久化?
后端
BD_Marathon5 小时前
【Flink】部署模式
java·数据库·flink
鼠鼠我捏,要死了捏8 小时前
深入解析Java NIO多路复用原理与性能优化实践指南
java·性能优化·nio
ningqw8 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友8 小时前
vi编辑器命令常用操作整理(持续更新)
后端
superlls8 小时前
(Redis)主从哨兵模式与集群模式
java·开发语言·redis
胡gh8 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫9 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong9 小时前
技术人如何对客做好沟通(上篇)
后端