深入剖析 Spring Bean 的生命周期

一、Spring Bean 生命周期整体概览

Spring Bean 的生命周期是一个从 "创建" 到 "销毁" 的完整过程,主要由 Spring IoC 容器负责管理。

核心流程:

  1. 实例化(Instantiation)

    • 容器通过反射机制调用Bean的构造函数(默认无参构造或通过@Autowired指定的构造方法)
    • 此时Bean对象已经被创建,但属性值均为null或默认值
    • 示例:对于UserService类,会先执行new UserService()操作
  2. 属性注入(Population)

    • 通过Setter方法(@Autowired标注)或字段直接注入(@Resource)完成依赖注入
    • 处理@Value注解的配置值注入
    • 解析并注入其他Bean的引用(如将UserDao注入到UserService中)
    • 此阶段可能出现循环依赖问题,Spring通过三级缓存机制解决
  3. 初始化(Initialization)

    • 执行Aware接口回调(BeanNameAware、BeanFactoryAware等)
    • 执行BeanPostProcessor的前置处理(postProcessBeforeInitialization)
    • 调用InitializingBean的afterPropertiesSet()方法
    • 执行自定义init方法(@PostConstruct或XML配置的init-method)
    • 执行BeanPostProcessor的后置处理(postProcessAfterInitialization)
    • 典型应用场景:数据库连接池在此阶段完成初始化
  4. 销毁(Destruction)

    • 容器关闭时触发(如调用ConfigurableApplicationContext.close())
    • 调用DisposableBean的destroy()方法
    • 执行自定义destroy方法(@PreDestroy或XML配置的destroy-method)
    • 典型应用:释放数据库连接、关闭文件流等资源清理

完整生命周期:

  1. 容器启动

    • 扫描@Component等注解或解析XML配置
    • 生成BeanDefinition元数据
  2. 实例化阶段

    // 示例:通过构造器实例化
    public class ExampleBean {
        public ExampleBean() {
            System.out.println("构造函数执行");
        }
    }
    

  3. 属性注入阶段

    • @Autowired自动装配
    • @Value("${property}")属性注入
    • 处理@Resource等注解
  4. 初始化阶段的关键步骤:

    • Aware接口回调(顺序):

      1. BeanNameAware.setBeanName()
      2. BeanClassLoaderAware.setBeanClassLoader()
      3. BeanFactoryAware.setBeanFactory()
      4. EnvironmentAware.setEnvironment()
      5. ApplicationContextAware.setApplicationContext()
    • 初始化回调(顺序):

      1. @PostConstruct方法
      2. InitializingBean.afterPropertiesSet()
      3. XML init-method
  5. 销毁阶段示例:

    @PreDestroy
    public void cleanup() {
        // 释放资源
        dataSource.close();
    }
    

  6. 扩展机制:

    • BeanPostProcessor:可干预所有Bean的初始化过程
    • InstantiationAwareBeanPostProcessor:可干预实例化过程
    • DestructionAwareBeanPostProcessor:可干预销毁过程

二、Spring Bean 生命周期各阶段详细解析

1. 阶段 1:Bean 定义的加载与解析(详细扩展)

在 Spring IoC 容器启动时,Bean 定义的加载与解析是首要步骤,具体过程如下:

1.1 定义来源的扩展说明

  • XML 配置文件:传统方式,通过 <beans> 根标签和嵌套的 <bean> 标签声明,支持属性注入(<property>)、构造函数注入(<constructor-arg>)等配置。示例:
    <beans>
      <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
      </bean>
    </beans>
    

  • 注解扫描:通过 @ComponentScan 开启组件扫描,结合 @Component 及其派生注解(如 @Service)自动注册 Bean。支持 @Autowired 实现依赖注入。
  • Java 配置类:使用 @Configuration 标记配置类,通过 @Bean 方法显式定义 Bean。适用于需要编程式初始化的场景,如第三方库集成:
    @Configuration
    public class AppConfig {
      @Bean
      public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        return ds;
      }
    }
    

1.2 BeanDefinition 的深层解析

  • 属性存储BeanDefinition 存储了类名(className)、作用域(scope)、延迟初始化(lazyInit)等元数据。对于 XML 配置,BeanDefinitionReader 会将 <bean> 标签解析为 GenericBeanDefinition
  • 合并过程:若存在父子 Bean 定义(通过 parent 属性指定),子定义会继承父定义的属性,最终生成 RootBeanDefinition
  • 注册时机BeanDefinition 通常在容器启动时(如 ApplicationContext.refresh())被加载到 DefaultListableBeanFactorybeanDefinitionMap 中。

2. 阶段 2:实例化(Instantiation)(扩展细节)

2.1 实例化策略

  • 反射调用:默认通过 Class.newInstance() 或构造函数反射(Constructor.newInstance())创建实例。若类无公开无参构造器,需在配置中指定参数,如:
    <bean id="userService" class="com.example.UserService">
      <constructor-arg ref="userRepository"/>
    </bean>
    

  • 工厂方法:支持静态工厂(factory-method)和实例工厂(factory-bean)创建对象。例如创建单例工具类:
    public class Utils {
      private static final Utils INSTANCE = new Utils();
      public static Utils getInstance() { return INSTANCE; }
    }
    

    <bean id="utils" class="com.example.Utils" factory-method="getInstance"/>
    

2.2 循环依赖的实例化处理

  • 三级缓存机制:Spring 通过 singletonFactoriesearlySingletonObjectssingletonObjects 三级缓存解决单例 Bean 的循环依赖。在实例化后、注入属性前,会将未完成的 Bean 存入缓存供其他 Bean 引用。

3. 阶段 3:属性注入(Population)(扩展示例)

3.1 注入方式对比

方式配置示例适用场景
Setter 注入<property name="dao" ref="userDao"/>可选依赖或需动态变更的属性
构造器注入<constructor-arg ref="userDao"/>强制依赖或不可变对象
注解字段注入@Autowired private UserDao dao;代码简洁,常用于现代 Spring

3.2 复杂类型注入

  • 集合注入:支持 <list><map> 等标签配置集合属性:
    <bean id="complexBean" class="com.example.ComplexBean">
      <property name="names">
        <list>
          <value>Alice</value>
          <value>Bob</value>
        </list>
      </property>
    </bean>
    

  • SpEL 表达式:通过 #{...} 实现动态值注入,如:
    <property name="timeout" value="#{systemProperties['default.timeout'] ?: 30}"/>
    

4. 阶段 4:Aware 接口回调(扩展接口列表)

4.1 完整 Aware 接口体系

接口回调方法典型应用场景
EmbeddedValueResolverAwaresetEmbeddedValueResolver()解析 ${...} 占位符
MessageSourceAwaresetMessageSource()国际化消息处理
ApplicationEventPublisherAwaresetApplicationEventPublisher()发布应用事件

4.2 实现原理

通过 ApplicationContextAwareProcessor 检测并调用 Aware 接口,该处理器在 AbstractApplicationContext.prepareBeanFactory() 中被注册。

5. 阶段 5:BeanPostProcessor 前置处理(扩展应用)

5.1 典型实现类

  • CommonAnnotationBeanPostProcessor:处理 @PostConstruct@PreDestroy 等 JSR-250 注解。
  • AutowiredAnnotationBeanPostProcessor:处理 @Autowired@Value 注入。
  • AbstractAdvisingBeanPostProcessor:AOP 代理的基础实现类。

5.2 自定义处理器示例

实现属性加密解密:

public class EncryptProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String name) {
    if (bean instanceof SensitiveData) {
      ((SensitiveData) bean).decryptFields();
    }
    return bean;
  }
}

6. 阶段 6:初始化(Initialization)——Bean 功能完善

初始化阶段是对 Bean 实例进行功能完善的关键环节,主要包括以下两个步骤(按顺序执行):

(1)执行 InitializingBean 接口的 afterPropertiesSet() 方法

若 Bean 实现了 InitializingBean 接口,Spring 会调用其 afterPropertiesSet() 方法。该方法的作用是在 Bean 的属性全部注入完成后,执行一些初始化逻辑(如初始化连接池、加载配置文件等)。这个阶段主要用于:

  • 验证依赖注入是否完整
  • 初始化资源(如数据库连接池)
  • 加载配置文件
  • 执行其他必须在属性注入完成后才能进行的操作

示例:让 UserService 实现 InitializingBean

public class UserService implements BeanNameAware, ApplicationContextAware, InitializingBean {
    
    private UserDao userDao;
    
    // ... 其他属性和方法
    
    // 实现InitializingBean接口的方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("UserService:InitializingBean回调(afterPropertiesSet())——属性注入完成后执行");
        // 此处可编写初始化逻辑,如校验userDao是否注入成功
        if (userDao == null) {
            throw new RuntimeException("UserDao未注入!");
        }
        // 初始化缓存
        initCache();
        // 加载配置文件
        loadConfig();
    }
    
    private void initCache() {
        // 初始化缓存逻辑
    }
    
    private void loadConfig() {
        // 加载配置文件逻辑
    }
}

(2)执行自定义初始化方法(init-method)

除了实现 InitializingBean 接口,我们还可以通过配置指定自定义的初始化方法(如 XML 中的 init-method 属性、注解 @PostConstruct)。Spring 会在 afterPropertiesSet() 方法执行完成后,调用该自定义初始化方法。

需要注意的要点:

  1. 方法签名要求:自定义初始化方法必须是无参数、无返回值,且不能抛出 checked 异常的方法
  2. 执行顺序
    • 若同时实现 InitializingBean 和配置 init-method,则 afterPropertiesSet() 先执行,自定义初始化方法后执行
    • JSR-250 规范中的 @PostConstruct 注解,其作用与 init-method 一致,且优先级高于 init-method(Spring 会先执行 @PostConstruct 标注的方法,再执行 init-method)
  3. 配置方式
    • XML 配置:<bean id="userService" class="com.example.UserService" init-method="init"/>
    • Java 配置:@Bean(initMethod = "init")
    • 注解配置:@PostConstruct

示例:通过 @PostConstruct 指定自定义初始化方法

public class UserService {
    
    // ... 其他属性和方法
    
    @PostConstruct
    public void postConstructInit() {
        System.out.println("UserService:@PostConstruct注解初始化方法");
        // 初始化日志系统
        initLogger();
    }
    
    public void init() {
        System.out.println("UserService:自定义初始化方法(init-method)");
        // 初始化统计数据
        initStatistics();
    }
    
    private void initLogger() {
        // 日志系统初始化逻辑
    }
    
    private void initStatistics() {
        // 统计数据初始化逻辑
    }
}

7. 阶段 7:BeanPostProcessor 后置处理 —— 初始化后的增强

初始化阶段完成后,Spring 会再次遍历所有的 BeanPostProcessor 实例,调用其 postProcessAfterInitialization(Object bean, String beanName) 方法,对 Bean 实例进行初始化后的增强处理。

关键特性:

  1. AOP 实现的核心:这一阶段是 Spring 实现 AOP 的核心环节 —— Spring 会在此处为需要代理的 Bean 生成代理对象(如基于 JDK 动态代理或 CGLIB 代理),并将代理对象返回给容器。后续容器中存储和使用的,就是这个代理对象。
  2. 处理顺序:在所有初始化回调(InitializingBean.afterPropertiesSet、@PostConstruct、init-method)完成后执行
  3. 应用场景
    • AOP 代理创建
    • 对象包装(如返回装饰器模式实现的包装对象)
    • 性能监控代理
    • 缓存代理

示例:

public class AopProxyPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 检查是否需要为这个bean创建代理
        if (needProxy(bean)) {
            // 创建代理对象
            return createProxy(bean);
        }
        return bean;
    }
    
    private boolean needProxy(Object bean) {
        // 判断是否需要代理的逻辑
        return bean.getClass().isAnnotationPresent(Transactional.class);
    }
    
    private Object createProxy(Object target) {
        // 创建代理的逻辑
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 添加事务处理逻辑
                    System.out.println("开启事务...");
                    Object result = method.invoke(target, args);
                    System.out.println("提交事务...");
                    return result;
                }
            });
    }
}

例如,若 UserService 需要被 AOP 代理(如添加事务切面),则 postProcessAfterInitialization() 方法会返回 UserService 的代理对象,而非原实例。

8. 阶段 8:Bean 就绪 —— 可被应用程序使用

经过上述所有阶段后,Bean 实例已完全初始化完成,具备了完整的功能,此时会被存储在 Spring IoC 容器中,处于"就绪"状态。

获取和使用 Bean 的方式:

  1. 显式获取

    • 调用 ApplicationContext.getBean()BeanFactory.getBean() 方法
    • 通过 ConfigurableApplicationContext 获取
  2. 依赖注入

    • 通过 @Autowired 自动装配
    • 通过 @Resource 按名称装配
    • 通过 @Inject (JSR-330) 注解注入
    • XML 配置中的 <property><constructor-arg> 元素

示例:在 Controller 中注入 UserService 并使用

@Controller
@RequestMapping("/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // 使用就绪的UserService实例
        User user = userService.getUserById(id);
        if (user != null) {
            return ResponseEntity.ok(user);
        }
        return ResponseEntity.notFound().build();
    }
    
    @PostMapping
    public ResponseEntity<Void> createUser(@RequestBody User user) {
        // 使用就绪的UserService实例
        userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
}

Bean 就绪后的状态特征:

  1. 所有属性已正确注入
  2. 所有初始化回调已执行完毕
  3. 所有后置处理已完成
  4. 如果是需要代理的 Bean,已经是代理对象而非原始对象
  5. 已经注册到容器的单例缓存中(如果是单例作用域)

阶段 9:销毁(Destruction)—— 释放资源

当 Spring IoC 容器关闭时(如 Web 应用停止、调用 ApplicationContext.close() 方法),会对容器中的 Bean 进行销毁处理,释放资源。

(1)执行 DisposableBean 接口的 destroy() 方法

若 Bean 实现了 DisposableBean 接口,Spring 会调用其 destroy() 方法,执行销毁逻辑(如关闭数据库连接、释放线程池、删除临时文件等)。

典型应用场景:

  • 关闭数据库连接池
  • 释放网络连接
  • 清理线程池
  • 删除临时文件
  • 持久化缓存数据
  • 注销监听器

(2)执行自定义销毁方法(destroy-method)

与初始化阶段类似,我们也可以通过配置指定自定义的销毁方法(如 XML 中的 destroy-method 属性、注解 @PreDestroy)。Spring 会在 destroy() 方法执行完成后,调用该自定义销毁方法。

需要注意的要点:

  1. 方法签名要求:自定义销毁方法必须是无参数、无返回值,且不能抛出 checked 异常的方法
  2. 执行顺序
    • 若同时实现 DisposableBean 和配置 destroy-method,则 destroy() 先执行,自定义销毁方法后执行
    • JSR-250 规范中的 @PreDestroy 注解,其作用与 destroy-method 一致,且优先级高于 destroy-method(Spring 会先执行 @PreDestroy 标注的方法,再执行 destroy-method)
  3. 作用域限制
    • 仅单例 Bean(默认作用域)会被 Spring 容器管理销毁过程
    • 原型 Bean(scope="prototype")的销毁由开发者手动管理,Spring 容器不会调用其销毁方法
  4. 配置方式
    • XML 配置:<bean id="userService" class="com.example.UserService" destroy-method="cleanup"/>
    • Java 配置:@Bean(destroyMethod = "cleanup")
    • 注解配置:@PreDestroy

示例:让 UserService 实现 DisposableBean 并使用 @PreDestroy

public class UserService implements InitializingBean, DisposableBean {
    
    private ExecutorService threadPool;
    private Connection connection;
    
    // ... 其他属性和方法
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("UserService:@PreDestroy注解销毁方法");
        // 清理缓存
        clearCache();
    }
    
    // 实现DisposableBean接口的方法
    @Override
    public void destroy() throws Exception {
        System.out.println("UserService:DisposableBean回调(destroy())");
        // 关闭线程池
        if (threadPool != null) {
            threadPool.shutdown();
        }
        // 关闭数据库连接
        if (connection != null) {
            connection.close();
        }
    }
    
    public void destroyMethod() {
        System.out.println("UserService:自定义销毁方法(destroy-method)");
        // 其他清理工作
        cleanTemporaryFiles();
    }
    
    private void clearCache() {
        // 清理缓存逻辑
    }
    
    private void cleanTemporaryFiles() {
        // 清理临时文件逻辑
    }
}

容器关闭的触发方式:

  1. 对于 Web 应用,容器会在应用停止时自动关闭
  2. 对于独立应用,可以通过以下方式关闭:
    • 调用 ConfigurableApplicationContext.close()
    • 注册 JVM 关闭钩子:context.registerShutdownHook()
  3. 在测试环境中,通常使用 try-with-resources 或 @DirtiesContext 注解

销毁阶段的最佳实践:

  1. 确保所有资源都被正确释放
  2. 避免在销毁方法中抛出异常,这可能导致其他 Bean 的销毁方法不被执行
  3. 对于原型作用域的 Bean,考虑实现自定义的生命周期管理接口
  4. 对于需要顺序销毁的 Bean,可以考虑实现 SmartLifecycle 接口来控制销毁顺序

三、Spring Bean 生命周期执行顺序验证

1. 测试代码实现

public class BeanLifecycleTest {
    
    public static void main(String[] args) {
        // 1. 启动Spring容器(使用ClassPathXmlApplicationContext加载XML配置)
        // 注意:这里使用ClassPathXmlApplicationContext而非ApplicationContext接口,
        // 因为需要调用close()方法显式关闭容器
        System.out.println("---------------------开始启动Spring容器---------------------");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 分隔线,标记容器启动完成
        System.out.println("---------------------Spring容器启动完成,Bean就绪---------------------");
        
        // 2. 获取并使用UserService Bean
        // 注意:这里使用getBean()方法获取Bean实例,触发Bean的延迟初始化(如果是非单例或延迟加载的Bean)
        UserService userService = context.getBean("userService", UserService.class);
        
        // 调用业务方法验证Bean功能正常
        userService.queryUser(); // queryUser()是UserService中定义的业务方法
        
        // 3. 关闭Spring容器(触发Bean销毁)
        System.out.println("---------------------Spring容器开始关闭---------------------");
        context.close(); // 调用close()方法会触发所有单例Bean的销毁回调
        System.out.println("---------------------Spring容器关闭完成---------------------");
    }
}

2. 测试类关键说明

  1. 容器选择

    • 使用ClassPathXmlApplicationContext而非ApplicationContext接口,因为需要调用close()方法
    • 也可以使用AnnotationConfigApplicationContext进行基于注解的配置测试
  2. 配置方式

    • XML配置示例(applicationContext.xml):
      <bean id="userService" class="com.example.UserServiceImpl" 
            init-method="customInit" destroy-method="customDestroy">
          <property name="userDao" ref="userDao"/>
      </bean>
      
      <bean id="myBeanPostProcessor" class="com.example.MyBeanPostProcessor"/>
      

  3. Bean实现要求

    • UserService需要实现InitializingBean, DisposableBean接口
    • 包含@PostConstruct@PreDestroy注解方法
    • 定义XML配置中指定的init-methoddestroy-method

3. 预期控制台输出结果(完整顺序)

---------------------开始启动Spring容器---------------------
UserService:实例化阶段(调用无参构造函数)
UserService:属性注入阶段(设置userDao)
UserService:BeanNameAware回调(Bean名称为:userService)
UserService:ApplicationContextAware回调(获取ApplicationContext实例)
MyBeanPostProcessor:postProcessBeforeInitialization(userService)——初始化前处理
UserService:@PostConstruct注解初始化方法
UserService:InitializingBean回调(afterPropertiesSet())——属性设置完成后执行
UserService:自定义初始化方法(init-method)
MyBeanPostProcessor:postProcessAfterInitialization(userService)——初始化后处理
---------------------Spring容器启动完成,Bean就绪---------------------
UserService:执行业务方法(queryUser())
---------------------Spring容器开始关闭---------------------
UserService:@PreDestroy注解销毁方法
UserService:DisposableBean回调(destroy())
UserService:自定义销毁方法(destroy-method)
---------------------Spring容器关闭完成---------------------

4. 测试结果分析

  1. 初始化阶段验证

    • 实例化 → 属性注入 → Aware接口回调 → BeanPostProcessor前置处理 → 初始化方法 → BeanPostProcessor后置处理
    • 初始化方法执行顺序:@PostConstructafterPropertiesSet()init-method
  2. 销毁阶段验证

    • 销毁方法执行顺序:@PreDestroydestroy()destroy-method
  3. 关键观察点

    • BeanPostProcessor的两次调用分别在初始化前后
    • 三种初始化方法的执行顺序
    • 三种销毁方法的执行顺序

四、关键注意事项与常见问题

4.1 单例 Bean 与原型 Bean 的生命周期差异

4.1.1 单例 Bean(scope="singleton")

  • 创建时机:容器启动时立即创建(默认情况下懒加载关闭),可通过配置lazy-init="true"改为延迟初始化
  • 销毁时机:容器关闭时自动销毁
  • 生命周期管理:整个生命周期由 Spring 容器全程管理
  • 实例数量:在整个容器生命周期内仅存在一个共享实例
  • 典型应用场景:无状态的工具类、配置类、服务类等

4.2.2 原型 Bean(scope="prototype")

  • 创建时机:容器不会在启动时自动创建实例,而是每次调用getBean()时都会创建一个全新的独立实例
  • 销毁时机:容器不会管理原型 Bean 的销毁,需要开发者自行处理资源释放
  • 生命周期特点
    • 属性注入和初始化阶段(@PostConstructInitializingBeaninit-method)会正常执行
    • 销毁阶段(DisposableBeandestroy()方法、@PreDestroy注解方法、destroy-method配置)均不会被容器调用
    • 长时间运行的原型 Bean 可能导致内存泄漏,需特别注意资源清理
  • 典型应用场景:需要保持独立状态的业务对象,如购物车、用户会话等

4.3.3 示例:配置原型 Bean

<!-- 原型Bean配置 -->
<bean id="prototypeBean" 
      class="com.example.bean.PrototypeBean" 
      scope="prototype" 
      init-method="init" 
      destroy-method="destroy"/>

测试代码:

// 每次调用都会创建新实例
PrototypeBean bean1 = context.getBean("prototypeBean");
PrototypeBean bean2 = context.getBean("prototypeBean");
System.out.println(bean1 == bean2); // 输出 false

// 容器关闭时不会调用destroy方法
context.close();

4.2 懒加载对 Bean 生命周期的影响

4.2.1 重要说明

  • 原型 Bean 的特殊性:原型 Bean 始终处于"懒加载"状态,lazy-init配置对其无效,因为原型 Bean 本身就是按需创建
  • 单例 Bean 的懒加载控制
    • 默认情况(lazy-init="false"):容器启动时完成实例化、属性注入和初始化("预初始化")
    • 设置lazy-init="true":延迟到第一次getBean()调用时才执行完整初始化流程

4.2.2 示例:配置懒加载单例 Bean

<bean id="lazyBean" 
      class="com.example.bean.LazyBean" 
      lazy-init="true"/>

4.2.3 懒加载的优缺点分析

优点

  • 减少应用启动时的初始化时间
  • 节省内存资源(延迟加载不立即使用的Bean)

缺点

  • 首次请求时可能产生延迟
  • 可能掩盖某些初始化阶段的错误

4.3 BeanPostProcessor 的执行范围

4.3.1 执行特点

  • 全局性:对所有 Spring 容器管理的 Bean 实例都生效(包括 Spring 内置 Bean)
  • 执行阶段
    • postProcessBeforeInitialization():在 Bean 初始化方法(@PostConstructInitializingBean等)之前执行
    • postProcessAfterInitialization():在 Bean 初始化方法之后执行

4.3.2 过滤处理建议

当需要对特定 Bean 进行处理时,应在方法内部通过以下方式进行判断:

public Object postProcessBeforeInitialization(Object bean, String beanName) {
    // 按类型过滤
    if (bean instanceof UserService) {
        // 特定处理逻辑
    }
    
    // 按名称过滤
    if ("userService".equals(beanName)) {
        // 特定处理逻辑
    }
    return bean;
}

4.4 构造函数注入与属性注入的生命周期顺序差异

4.4.1 构造函数注入特点

  • 执行时机:在 Bean 实例化阶段(调用构造函数时)完成依赖注入
  • 优点
    • 依赖关系明确,强制要求依赖项可用
    • 便于实现不可变对象
  • 缺点
    • 当依赖项较多时,构造函数会变得臃肿
    • 循环依赖问题更难解决

4.4.2 属性注入特点

  • 执行时机:在 Bean 实例化之后、Aware 接口回调之前完成
  • 优点
    • 代码更简洁
    • 更灵活地处理可选依赖
  • 缺点
    • 依赖关系不如构造函数注入明确
    • 可能导致部分空指针问题

4.4.3 示例对比

构造函数注入方式

public class UserService {
    private final UserDao userDao;
    
    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
        System.out.println("UserService:构造函数注入(实例化阶段)");
    }
}

属性注入方式

public class UserService {
    @Autowired
    private UserDao userDao;
    
    public UserService() {
        System.out.println("UserService:实例化(此时userDao尚未注入)");
    }
    
    @PostConstruct
    public void init() {
        System.out.println("UserService:初始化(此时userDao已注入)");
    }
}

五、Spring Boot 中 Bean 生命周期的变化

1. 简化配置,减少 XML 依赖

Spring Boot 通过注解驱动开发极大简化了 Bean 的配置方式:

  • 使用 @Component@Service@Repository@Controller 等注解替代传统 XML 中的 <bean> 定义
  • 自定义初始化和销毁方法时,推荐使用 JSR-250 标准注解:
    • @PostConstruct:替代 XML 中的 init-method 属性
    • @PreDestroy:替代 XML 中的 destroy-method 属性
  • 示例代码:
    @Service
    public class UserService {
        @PostConstruct
        public void init() {
            // 初始化逻辑
        }
        
        @PreDestroy
        public void cleanup() {
            // 资源释放逻辑
        }
    }
    

2. 自动配置类中的 Bean 生命周期

Spring Boot 自动配置机制对 Bean 生命周期的处理:

  1. 自动配置原理

    • 通过 spring-boot-autoconfigure 模块中的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件加载配置类
    • 例如 DataSourceAutoConfiguration 会自动配置数据源
  2. 自定义自动配置 Bean

    • 可通过 @Bean 注解的 initMethoddestroyMethod 属性指定方法
    • 或实现 InitializingBeanDisposableBean 接口
    • 完整配置示例:
      @Configuration
      public class DataSourceConfig {
          @Bean(initMethod = "init", destroyMethod = "close")
          public DataSource dataSource() {
              HikariDataSource dataSource = new HikariDataSource();
              dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
              dataSource.setUsername("root");
              dataSource.setPassword("password");
              dataSource.setMaximumPoolSize(20);
              return dataSource;
          }
      }
      

  3. 生命周期阶段

实例化 → 属性赋值 → 初始化前 (@PostConstruct) → 初始化 (InitializingBean) → 初始化后 (AOP 代理等) → 使用 → 销毁前 (@PreDestroy) → 销毁 (DisposableBean)

3. Spring Boot 2.x 中的注解兼容性

Java 9+ 的模块化影响

  • 问题背景
    • JSR-250 (javax.annotation 包) 在 Java 9 被标记为可选模块
    • 可能导致 @PostConstruct@PreDestroy 无法正常使用

解决方案

  1. Spring Boot 默认支持

    • 2.x 版本通过 spring-context 模块内置了这些注解的实现
    • 一般情况无需额外配置
  2. 特殊情况处理

    • 若出现注解失效,检查以下可能:
      <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.3.2</version>
      </dependency>
      

    • 或检查是否误排除了相关依赖:
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
              <!-- 确保没有排除 spring-context -->
          </exclusions>
      </dependency>
      

  3. 验证方式

    @SpringBootTest
    public class LifecycleTest {
        @Autowired
        private UserService userService;
        
        @Test
        public void testAnnotations() {
            // 测试会自动触发 @PostConstruct 方法
            assertNotNull(userService);
        }
    }
    

六、Bean 生命周期的实际应用场景

6.1资源初始化与释放

示例:初始化 Redis 连接

@Service
public class RedisService implements InitializingBean, DisposableBean {
    private Jedis jedis;
    private JedisPool jedisPool;
    
    @Value("${redis.host}")
    private String redisHost;
    
    @Value("${redis.port}")
    private int redisPort;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化阶段:创建Redis连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(50);
        poolConfig.setMaxIdle(20);
        jedisPool = new JedisPool(poolConfig, redisHost, redisPort);
        System.out.println("Redis连接池初始化完成,当前连接数:" + poolConfig.getMaxTotal());
        
        // 测试连接
        try (Jedis testConnection = jedisPool.getResource()) {
            System.out.println("Redis连接测试成功,PING响应:" + testConnection.ping());
        }
    }

    @Override
    public void destroy() throws Exception {
        // 销毁阶段:关闭连接池
        if (jedisPool != null && !jedisPool.isClosed()) {
            jedisPool.close();
            System.out.println("Redis连接池已关闭,释放所有连接");
        }
    }
    
    public Jedis getResource() {
        return jedisPool.getResource();
    }
}

典型应用场景

  1. 数据库连接管理

    • 在初始化阶段创建DataSource和连接池
    • 在销毁阶段关闭连接池,释放所有连接
    • 示例:HikariCP、DBCP等连接池的初始化
  2. 文件资源处理

    @Component
    public class FileProcessor {
        private BufferedWriter writer;
        
        @PostConstruct
        public void init() throws IOException {
            writer = new BufferedWriter(new FileWriter("app.log", true));
            writer.write("\n=== 应用启动于 " + new Date() + " ===\n");
            writer.flush();
        }
        
        @PreDestroy
        public void cleanup() throws IOException {
            writer.write("=== 应用关闭于 " + new Date() + " ===\n");
            writer.close();
        }
    }
    

  3. 线程池管理

    • 初始化时创建固定大小的线程池
    • 销毁时执行shutdownNow()并等待任务完成

6.2Bean 属性校验

示例:属性依赖校验

@Component
public class DependencyCheckBeanPostProcessor implements BeanPostProcessor {
    
    private static final Logger logger = LoggerFactory.getLogger(DependencyCheckBeanPostProcessor.class);
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 检查所有标有@Required注解的字段
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Required.class)) {
                field.setAccessible(true);
                try {
                    Object value = field.get(bean);
                    if (value == null) {
                        String errorMsg = String.format("Bean '%s'的必需字段'%s'未注入!", 
                            beanName, field.getName());
                        logger.error(errorMsg);
                        throw new IllegalStateException(errorMsg);
                    }
                } catch (IllegalAccessException e) {
                    logger.error("检查字段访问失败", e);
                }
            }
        }
        
        // 检查方法级别的@Required注解
        Method[] methods = bean.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Required.class) && 
                method.getParameterCount() == 1) {
                method.setAccessible(true);
                try {
                    Object value = method.invoke(bean);
                    if (value == null) {
                        String errorMsg = String.format("Bean '%s'的必需方法'%s'返回null!", 
                            beanName, method.getName());
                        logger.error(errorMsg);
                        throw new IllegalStateException(errorMsg);
                    }
                } catch (Exception e) {
                    logger.error("检查方法调用失败", e);
                }
            }
        }
        
        return bean;
    }
}

校验类型扩展

  1. 基本类型校验

    if (field.getType() == int.class && (int)field.get(bean) == 0) {
        throw new IllegalStateException("整型字段不能为0");
    }
    

  2. 集合校验

    if (field.getType().isAssignableFrom(Collection.class)) {
        Collection<?> collection = (Collection<?>) field.get(bean);
        if (collection == null || collection.isEmpty()) {
            throw new IllegalStateException("集合字段不能为空");
        }
    }
    

  3. 自定义注解校验

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ValidEmail {
        String message() default "无效的邮箱格式";
    }
    

6.3AOP 代理增强

代理创建过程详解

Spring通过AbstractAutoProxyCreator实现代理创建:

  1. 初始化阶段

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                // 包装目标对象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    

  2. 代理选择策略

    • 如果目标类实现了接口,默认使用JDK动态代理
    • 如果目标类没有实现接口,使用CGLIB代理
    • 可通过@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB

典型AOP应用场景

  1. 事务管理

    @Service
    public class OrderService {
        
        @Transactional
        public void createOrder(Order order) {
            // 业务逻辑
        }
    }
    

  2. 性能监控

    @Aspect
    @Component
    public class PerformanceMonitorAspect {
        
        @Around("execution(* com.example.service.*.*(..))")
        public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.currentTimeMillis();
            Object result = pjp.proceed();
            long duration = System.currentTimeMillis() - start;
            System.out.println(pjp.getSignature() + " executed in " + duration + "ms");
            return result;
        }
    }
    

  3. 安全控制

    @Aspect
    @Component
    public class SecurityAspect {
        
        @Before("@annotation(requiresPermission)")
        public void checkPermission(RequiresPermission requiresPermission) {
            if (!SecurityContext.hasPermission(requiresPermission.value())) {
                throw new AccessDeniedException("权限不足");
            }
        }
    }
    

  4. 缓存处理

    @Aspect
    @Component
    public class CacheAspect {
        
        @Around("@annotation(cacheable)")
        public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
            String key = generateKey(pjp, cacheable);
            Object value = cache.get(key);
            if (value == null) {
                value = pjp.proceed();
                cache.put(key, value, cacheable.ttl());
            }
            return value;
        }
    }
    

生命周期注意事项

  1. 代理对象与原对象

    • 容器中保存的是代理对象
    • 原对象被代理对象持有为目标源(target source)
    • 通过AopContext.currentProxy()可获取当前代理
  2. 初始化顺序

    @Service
    public class OrderService {
        
        @Autowired
        private PaymentService paymentService; // 注入的是代理对象
        
        @PostConstruct
        public void init() {
            // 此时所有依赖都是代理对象
        }
    }
    

  3. 自调用问题

    public void methodA() {
        methodB(); // 直接调用会绕过代理
        ((OrderService)AopContext.currentProxy()).methodB(); // 正确方式
    }
    
    @Transactional
    public void methodB() {
        // 事务逻辑
    }
    

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值