📌 写在前面

        刚开始学Java时,我们习惯这样创建对象:User user = new User()。后来学Spring,发现不用自己new了,在类上加个@Component,就能通过@Autowired拿到实例。这个被Spring管理的对象就叫Bean

        但很多初学者会困惑:Bean不就是普通对象吗?为什么要叫Bean?它和JavaBean有什么关系?为什么面试总爱问作用域和生命周期?

        其实,理解Spring Bean的关键在于:它不再是你手动new出来的“孤立对象”,而是被Spring IoC容器接管了生命周期的“托管对象”。容器帮你创建、装配、管理依赖、控制销毁。

        这篇笔记,我按照大多数Java学习者的认知顺序来组织:从JavaBean到Spring Bean,从注解声明到XML配置,从单例到原型,从初始化到销毁。希望能帮你彻底搞懂Spring Bean的本质。

1️⃣ 回顾JavaBean:只是一种编码规范

在Spring出现之前,JavaBean就已经存在了。它并不是一个技术,而是一套编码规范

  • 类必须是公共的public

  • 有一个无参构造方法

  • 属性用private修饰,通过getter/setter访问

  • 通常实现Serializable接口(用于传输/存储)

public class User implements Serializable {
    private String name;
    private Integer age;
    
    public User() {}  // 无参构造
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // ...其他getter/setter
}

JavaBean的作用很简单:封装数据,比如从前端接收表单数据、ORM框架映射数据库记录。它只是一个普通类,需要你自己new出来。

2️⃣ Spring Bean:被IoC容器管理的对象

Spring Bean是一个被Spring IoC容器实例化、装配和管理的对象。你不再需要手动new,而是告诉Spring:“这个类交给你管理”,然后从容器中取用。

最简单的声明:注解

@Component  // 标记为Spring Bean
public class UserService {
    public void doSomething() {
        System.out.println("Hello Bean");
    }
}

在其他地方使用:

@Autowired
private UserService userService;  // Spring自动注入

背后发生了什么?

  1. Spring启动时,扫描带有@Component(或其派生注解@Service@Repository@Controller)的类

  2. 通过反射创建该类的实例(默认单例)

  3. 将实例存入IoC容器(一个大的Map结构)

  4. 遇到@Autowired时,从容器中查找匹配的Bean并注入

关键区别:普通类是你主动new,Spring Bean是被动创建并由容器管理生命周期。

3️⃣ Bean与普通类的本质区别

4️⃣ Bean的三种声明方式

虽然现代项目几乎都用注解,但理解XML和Java配置有助于深入理解Spring。

方式一:XML配置(老项目常见)

<beans>
    <bean id="userService" class="com.example.UserService"/>
    <bean id="userDao" class="com.example.UserDao"/>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService service = context.getBean(UserService.class);

方式二:注解(最常用)

  • @Component:通用Bean

  • @Service:业务层

  • @Repository:数据访问层

  • @Controller:控制层(Web)

@Service
public class UserService { ... }

启用组件扫描(Spring Boot自动配置):

@SpringBootApplication  // 内含@ComponentScan

方式三:Java配置类(Spring Boot推荐用于第三方Bean)

@Configuration
public class AppConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        return new RedisTemplate<>();
    }
}

选型建议:自己写的类用@Component系列;第三方库、需要复杂初始化的用@Bean

5️⃣ Bean的作用域

作用域决定了Bean实例的创建策略。默认是单例(singleton)

声明方式

@Component
@Scope("prototype")
public class ShoppingCart { ... }

重要提示:当单例Bean依赖原型Bean时,原型Bean会失效(因为单例只创建一次)。解决方案:使用@Lookup注解或ObjectFactory

6️⃣ Bean的生命周期

面试高频考点。Bean在IoC容器中的完整生命周期如下:

实例化 → 属性注入 → 初始化前 → 初始化 → 初始化后 → 使用 → 销毁前 → 销毁

各阶段关键点

示例:完整生命周期演示

@Component
public class ExampleBean implements InitializingBean, DisposableBean {
    
    @PostConstruct
    public void postConstruct() {
        System.out.println("1. @PostConstruct");
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("2. InitializingBean");
    }
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("3. @PreDestroy");
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("4. DisposableBean");
    }
}

执行顺序@PostConstruct → afterPropertiesSet → (使用中) → @PreDestroy → destroy

大多数业务开发不需要手动实现这些接口,但了解它们有助于排查问题(比如为什么某个Bean初始化时报错)。

7️⃣ 常见面试题与避坑指南

Q1:Spring Bean是线程安全的吗?

A:默认单例,如果Bean中没有可变的成员变量(或者使用ThreadLocal),则是线程安全的。ServiceDao通常无状态,所以安全。如果Bean有状态(如成员变量int count),则需要加锁或改为原型作用域。

Q2:@Component@Bean的区别?

A

  • @Component:用在自己写的类上,配合@ComponentScan自动注册

  • @Bean:用在配置类的方法上,适合创建第三方库的实例,可以自定义初始化逻辑

Q3:如何让一个Bean在初始化后执行某个方法?

A

  • 使用@PostConstruct注解(推荐)

  • 实现InitializingBean接口

  • @Bean(initMethod = "customInit")

Q4:Bean的作用域什么时候用prototype

A:当Bean持有用户特定状态时,比如购物车、表单对象。注意prototype的Bean不会自动销毁,需要手动管理。

避坑指南

  1. 不要在单例Bean中直接注入原型Bean,否则原型Bean退化为单例。应使用@LookupObjectProvider

  2. 避免在构造方法中执行过多逻辑,因为此时依赖可能尚未注入。

  3. @PostConstruct@PreDestroy需要引入javax.annotation-api依赖(JDK9后需单独加),但Spring Boot已默认包含。

  4. 理解循环依赖:Spring通过三级缓存解决了单例Bean的循环依赖,但不支持构造器循环依赖和原型Bean的循环依赖。

📌 写在最后

        从普通Java对象到Spring Bean,本质上是控制权的转移:你不再主动创建对象,而是把创建和管理的权力交给Spring IoC容器。这个转变一开始可能不习惯,但随着项目变大,你会发现它能带来极大的便利:解耦、统一管理、AOP增强。

理解Spring Bean的关键,是记住三件事:

  1. 它是被容器管理的对象

  2. 默认是单例

  3. 生命周期有明确的回调点

        希望这篇笔记能帮你从“只会用注解”到“真正理解Bean的本质”。下一篇,我计划写一篇关于Spring循环依赖的解决方案与原理,敬请期待。

        如果一个单例Bean中通过@Autowired注入了一个原型Bean,那么这个原型Bean每次调用时都是新实例吗?为什么?如何解决?欢迎在评论区留下你的答案

        如果这篇文章帮到了你,欢迎点赞+收藏+关注,我会持续输出相关的高质量内容。你的支持是我创作的动力!

更多推荐