本文收录于「Java 学习日记」专栏,聚焦 Spring 框架的核心载体 ——Bean,从生命周期全流程、作用域控制到多种装配方式,帮你彻底搞懂 “Spring 如何管理对象”,夯实框架底层认知~

一、为什么要深入学习 Spring Bean?

在上一篇注解学习中,我们知道@Service/@Component能让 Spring 管理对象,但背后有三个核心问题需要搞清楚:

  • 生命周期:Spring 创建 Bean 时,从 “出生” 到 “销毁” 经历了哪些阶段?如何在关键节点自定义逻辑?
  • 作用域:为什么默认 Bean 是单例?原型 Bean 和单例 Bean 的使用场景有何不同?
  • 装配方式:除了@Autowired,还有哪些方式能把 Bean 组装到一起?不同方式的优缺点是什么?

理解这些问题,能帮你:

  1. 解决 Bean 注入失败、生命周期方法不执行等常见问题;
  2. 根据业务场景选择合适的 Bean 作用域,避免线程安全问题;
  3. 灵活选择 Bean 装配方式,适配不同的开发场景(如第三方类集成)。

今天这篇日记,我们从 “生命周期→作用域→装配方式” 逐步拆解,结合实战案例让你对 Spring Bean 建立完整认知。

二、Spring Bean 的完整生命周期(核心)

Spring Bean 的生命周期是指从容器启动创建 Bean容器关闭销毁 Bean的全流程,核心分为 8 个阶段(单例 Bean),用流程图直观展示:

预览

graph TD
    A[1. 实例化Bean] --> B[2. 设置Bean属性(依赖注入)]
    B --> C[3. 调用BeanNameAware的setBeanName()]
    C --> D[4. 调用BeanFactoryAware的setBeanFactory()]
    D --> E[5. 调用ApplicationContextAware的setApplicationContext()]
    E --> F[6. 调用BeanPostProcessor的postProcessBeforeInitialization()]
    F --> G[7. 调用InitializingBean的afterPropertiesSet() / @PostConstruct]
    G --> H[8. 调用BeanPostProcessor的postProcessAfterInitialization()]
    H --> I[9. Bean就绪,对外提供使用]
    I --> J[10. 容器关闭:调用DisposableBean的destroy() / @PreDestroy]

1. 生命周期核心阶段详解

阶段 核心操作 自定义方式
实例化 Bean Spring 通过反射创建 Bean 对象(调用无参构造) 无(由 Spring 自动完成)
设置 Bean 属性 注入依赖的 Bean(如@Autowired注入) 无(依赖注入逻辑)
感知容器(Aware 接口) Bean 感知自身在容器中的信息(如 Bean 名称、容器对象) 实现BeanNameAware/BeanFactoryAware等接口
初始化前处理 全局 Bean 后置处理器,对所有 Bean 生效 实现BeanPostProcessor接口,重写postProcessBeforeInitialization
初始化 Bean 初始化核心阶段,执行自定义初始化逻辑 1. 实现InitializingBean接口;2. 标注@PostConstruct;3. XML 配置init-method
初始化后处理 全局 Bean 后置处理器,初始化后增强 Bean 实现BeanPostProcessor接口,重写postProcessAfterInitialization
就绪使用 Bean 对外提供服务(如被注入到 Controller/Service) 业务代码调用 Bean 方法
销毁 容器关闭时执行清理逻辑 1. 实现DisposableBean接口;2. 标注@PreDestroy;3. XML 配置destroy-method

2. 生命周期实战案例

步骤 1:编写自定义 Bean,实现生命周期接口 + 注解

java

运行

package com.example.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component("userBean")
public class UserBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {

    private String username;

    // 1. 无参构造(实例化阶段调用)
    public UserBean() {
        System.out.println("【1. 实例化】UserBean无参构造被调用");
    }

    // 2. Setter方法(属性注入阶段调用)
    public void setUsername(String username) {
        this.username = username;
        System.out.println("【2. 设置属性】注入username:" + username);
    }

    // 3. BeanNameAware:获取Bean名称
    @Override
    public void setBeanName(String name) {
        System.out.println("【3. 感知BeanName】Bean名称:" + name);
    }

    // 4. BeanFactoryAware:获取BeanFactory
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("【4. 感知BeanFactory】BeanFactory:" + beanFactory.getClass().getSimpleName());
    }

    // 5. ApplicationContextAware:获取ApplicationContext
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("【5. 感知ApplicationContext】容器:" + applicationContext.getClass().getSimpleName());
    }

    // 6. 初始化前(@PostConstruct之前)
    @PostConstruct
    public void postConstruct() {
        System.out.println("【7. 初始化-@PostConstruct】执行初始化逻辑");
    }

    // 7. InitializingBean:属性设置完成后初始化
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【7. 初始化-afterPropertiesSet】执行初始化逻辑");
    }

    // 自定义初始化方法(XML配置init-method时用)
    public void initMethod() {
        System.out.println("【7. 初始化-initMethod】执行自定义初始化");
    }

    // 业务方法
    public void sayHello() {
        System.out.println("【9. 就绪使用】Hello " + username);
    }

    // 8. DisposableBean:销毁Bean
    @Override
    public void destroy() throws Exception {
        System.out.println("【10. 销毁-destroy】执行销毁逻辑");
    }

    // 自定义销毁方法(XML配置destroy-method时用)
    @PreDestroy
    public void preDestroy() {
        System.out.println("【10. 销毁-@PreDestroy】执行销毁逻辑");
    }
}

步骤 2:编写 Bean 后置处理器(全局生效)

java

运行

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    // 初始化前处理
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if ("userBean".equals(beanName)) {
            System.out.println("【6. 初始化前处理】BeanPostProcessor-before");
        }
        return bean;
    }

    // 初始化后处理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("userBean".equals(beanName)) {
            System.out.println("【8. 初始化后处理】BeanPostProcessor-after");
        }
        return bean;
    }
}

步骤 3:配置类 + 测试类

java

运行

// 配置类
package com.example.config;

import com.example.bean.UserBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example") // 扫描注解
public class SpringConfig {

    // 注册Bean并设置属性
    @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    public UserBean userBean() {
        UserBean userBean = new UserBean();
        userBean.setUsername("Java日记");
        return userBean;
    }
}

// 测试类
package com.example.test;

import com.example.bean.UserBean;
import com.example.config.SpringConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanLifeCycleTest {
    public static void main(String[] args) {
        // 1. 创建容器(触发Bean实例化、初始化)
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        // 2. 获取Bean并使用
        UserBean userBean = context.getBean(UserBean.class);
        userBean.sayHello();

        // 3. 关闭容器(触发Bean销毁)
        context.close();
    }
}

步骤 4:运行结果(核心流程验证)

plaintext

【1. 实例化】UserBean无参构造被调用
【2. 设置属性】注入username:Java日记
【3. 感知BeanName】Bean名称:userBean
【4. 感知BeanFactory】BeanFactory:DefaultListableBeanFactory
【5. 感知ApplicationContext】容器:AnnotationConfigApplicationContext
【6. 初始化前处理】BeanPostProcessor-before
【7. 初始化-@PostConstruct】执行初始化逻辑
【7. 初始化-afterPropertiesSet】执行初始化逻辑
【7. 初始化-initMethod】执行自定义初始化
【8. 初始化后处理】BeanPostProcessor-after
【9. 就绪使用】Hello Java日记
【10. 销毁-@PreDestroy】执行销毁逻辑
【10. 销毁-destroy】执行销毁逻辑

3. 生命周期关键注意点

  1. 初始化方法优先级@PostConstruct > InitializingBean.afterPropertiesSet() > XML/@Bean指定的init-method
  2. 销毁方法优先级@PreDestroy > DisposableBean.destroy() > XML/@Bean指定的destroy-method
  3. 原型 Bean 生命周期:原型 Bean 仅执行 “实例化→属性设置→初始化”,容器不管理销毁(需手动调用);
  4. BeanPostProcessor:对容器中所有 Bean 生效,是 Spring AOP、注解解析的核心底层机制。

三、Spring Bean 的作用域(Scope)

Bean 的作用域决定了Bean 实例的创建时机和存活范围,Spring 提供 5 种核心作用域(常用 2 种):

作用域 中文名称 核心特点 适用场景
singleton 单例(默认) 容器启动时创建,全局唯一,容器关闭时销毁 无状态 Bean(如 Service、DAO、工具类)
prototype 原型 每次getBean()/ 注入时创建新实例,容器不管理销毁 有状态 Bean(如含成员变量的对象、请求级数据载体)
request 请求级 Web 环境下,每个 HTTP 请求创建一个实例 Web 请求级数据(如请求参数封装)
session 会话级 Web 环境下,每个 HTTP 会话创建一个实例 会话级数据(如用户登录信息)
application 应用级 Web 环境下,整个 Web 应用共享一个实例(等价于 singleton) 全局应用配置

1. 作用域实战(单例 vs 原型)

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

java

运行

// 单例Bean(默认)
@Component
@Scope("singleton")
public class SingletonBean {
    private int count = 0;

    public void add() {
        count++;
        System.out.println("单例Bean count:" + count);
    }
}

// 原型Bean
@Component
@Scope("prototype")
public class PrototypeBean {
    private int count = 0;

    public void add() {
        count++;
        System.out.println("原型Bean count:" + count);
    }
}

步骤 2:测试作用域差异

java

运行

public class BeanScopeTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        // 单例Bean:多次获取是同一个对象
        SingletonBean s1 = context.getBean(SingletonBean.class);
        SingletonBean s2 = context.getBean(SingletonBean.class);
        s1.add(); // 输出:单例Bean count:1
        s2.add(); // 输出:单例Bean count:2
        System.out.println("单例Bean是否同一对象:" + (s1 == s2)); // true

        // 原型Bean:多次获取是不同对象
        PrototypeBean p1 = context.getBean(PrototypeBean.class);
        PrototypeBean p2 = context.getBean(PrototypeBean.class);
        p1.add(); // 输出:原型Bean count:1
        p2.add(); // 输出:原型Bean count:1
        System.out.println("原型Bean是否同一对象:" + (p1 == p2)); // false

        context.close();
    }
}

2. 作用域避坑指南

  1. 单例 Bean 线程安全:单例 Bean 是全局共享的,若包含可变成员变量(如用户信息),会导致线程安全问题,解决方案:
    • 避免在单例 Bean 中定义可变成员变量;
    • 改用原型 Bean;
    • 使用 ThreadLocal 存储线程私有数据。
  2. 原型 Bean 注入陷阱:单例 Bean 注入原型 Bean 时,原型 Bean 只会被注入一次(单例 Bean 初始化时),后续调用仍是同一个原型 Bean,解决方案:

    java

    运行

    // 单例Bean中获取原型Bean(每次获取新实例)
    @Component
    public class SingletonBean {
        @Autowired
        private ApplicationContext context;
    
        public PrototypeBean getPrototypeBean() {
            return context.getBean(PrototypeBean.class);
        }
    }
    
  3. Web 作用域使用条件request/session作用域仅在 Web 环境下生效,需引入 Spring Web 依赖,否则报错。

四、Spring Bean 的装配方式(依赖注入)

Bean 装配是指 “将依赖的 Bean 注入到目标 Bean 中”,Spring 提供 4 种核心装配方式,适配不同场景:

1. 手动装配(XML 配置)

适用场景:老项目、需要精确控制 Bean 创建逻辑的场景。

xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1. 构造器装配 -->
    <bean id="userDao" class="com.example.dao.UserDaoImpl"/>
    <bean id="userService" class="com.example.service.UserServiceImpl">
        <constructor-arg ref="userDao"/> <!-- 构造器注入 -->
    </bean>

    <!-- 2. Setter装配 -->
    <bean id="userController" class="com.example.controller.UserController">
        <property name="userService" ref="userService"/> <!-- Setter注入 -->
    </bean>
</beans>

2. 注解装配(主流)

适用场景:新项目、快速开发,核心注解:@Autowired/@Resource/@Qualifier

java

运行

@Service
public class UserServiceImpl implements UserService {
    // 方式1:字段注入(最简洁,推荐)
    @Autowired
    private UserDao userDao;

    // 方式2:Setter注入(兼容老版本)
    // @Resource(name = "userDao")
    // public void setUserDao(UserDao userDao) {
    //     this.userDao = userDao;
    // }

    // 方式3:构造器注入(推荐用于必须依赖)
    // public UserServiceImpl(@Qualifier("userDao") UserDao userDao) {
    //     this.userDao = userDao;
    // }
}

3. Java 配置装配(@Bean)

适用场景:第三方类集成(如数据源、RedisTemplate)、需要手动创建 Bean 的场景。

java

运行

@Configuration
public class BeanConfig {
    // 装配第三方类(如Druid数据源)
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    // 依赖注入:方法参数自动装配
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

4. 自动装配(autowire)

适用场景:简化 XML 配置,Spring 自动匹配依赖(不推荐,可读性差)。

xml

<!-- autowire="byType":按类型自动装配 -->
<bean id="userService" class="com.example.service.UserServiceImpl" autowire="byType"/>
<!-- autowire="byName":按名称自动装配(匹配setter方法名) -->
<bean id="userController" class="com.example.controller.UserController" autowire="byName"/>

5. 装配方式对比与最佳实践

装配方式 优点 缺点 最佳实践
字段注入(@Autowired) 代码简洁,开发效率高 无法注入 final 字段,单元测试需 Mock 日常业务开发(Service/Controller)
构造器注入 支持 final 字段,依赖明确,线程安全 依赖多时代码冗长 核心组件(如配置类、第三方集成)
Setter 注入 可选依赖,灵活配置 依赖可被多次修改,易出错 非必须依赖的场景
Java 配置(@Bean) 控制 Bean 创建全流程,适配第三方类 代码量略多 第三方组件集成(数据源、连接池)

五、今日实战小任务

  1. 基于生命周期案例,新增一个BeanPostProcessor,实现 “给所有 Bean 的 username 属性添加前缀【Spring】”;
  2. 编写一个单例 Bean,内部包含可变成员变量,模拟多线程访问,验证线程安全问题,并通过 ThreadLocal 解决;
  3. 用 4 种装配方式(XML、字段注入、构造器注入、@Bean)分别实现 “UserController → UserService → UserDao” 的依赖注入。

总结

  1. Spring Bean 生命周期核心流程:实例化→属性注入→感知容器→初始化前处理→初始化→初始化后处理→就绪使用→销毁,可通过@PostConstruct/InitializingBean等自定义初始化逻辑;
  2. Bean 作用域中,singleton(单例)是默认值,适用于无状态 Bean,prototype(原型)适用于有状态 Bean,需注意原型 Bean 注入单例 Bean 的陷阱;
  3. Bean 装配方式有 4 种:XML 手动装配、注解装配(主流)、Java 配置装配、自动装配,字段注入适合日常开发,构造器注入适合核心组件,@Bean 适合第三方类集成;
  4. 核心避坑点:单例 Bean 的线程安全问题、原型 Bean 的注入陷阱、生命周期方法的执行顺序。

下一篇【Day41】预告:SpringMVC 核心流程:DispatcherServlet 请求处理全解析,关注专栏从 Spring 核心过渡到 Web 框架实战~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐