Spring 中 @Bean、@Component 与 @Configuration 的作用及底层逻辑

Spring 中 @Bean、@Component 与 @Configuration 的作用及底层逻辑

一、为什么需要这些注解

在普通 Java 程序中,对象通常由开发者自己创建,例如:

java 复制代码
Coffee coffee = new Coffee();

这种方式下,对象的创建、依赖关系和生命周期都由代码自己管理。Spring 的核心思想是把这些工作交给 IoC 容器完成。所谓 IoC(Inversion of Control,控制反转),可以简单理解为:对象不再由业务代码主动 new,而是由 Spring 统一创建、保存、注入和销毁。

被 Spring 容器管理的对象称为 Bean。因此,@Component、@Configuration 和 @Bean 的本质作用,都是帮助 Spring 发现并注册 Bean,只是使用方式和适用场景不同。

二、@Component:让 Spring 自动发现并创建对象

@Component 写在类上,表示这个类需要交给 Spring 管理。

java 复制代码
@Component
public class CoffeeService {
    public void makeCoffee() {
        System.out.println("制作咖啡");
    }
}

Spring 启动时会根据组件扫描规则查找带有 @Component 的类,然后生成对应的 Bean 定义,并通过反射创建对象,最后放入 IoC 容器。

其大致流程如下:

text 复制代码
Spring 启动
→ 扫描指定包
→ 发现 @Component
→ 创建 BeanDefinition
→ 反射创建对象
→ 放入 IoC 容器

BeanDefinition 可以理解为对象的"创建说明书",其中记录 Bean 的名称、类型、作用域、初始化方式等信息。默认情况下,类名首字母小写后会作为 Bean 名称,例如 CoffeeService 对应的默认 Bean 名称通常是 coffeeService。

@Service、@Repository 和 @Controller 本质上都是 @Component 的特殊形式,只是用于表达不同层次的语义:

java 复制代码
@Service
public class UserService {
}

@Repository
public class UserRepository {
}

@Controller
public class UserController {
}

它们都能让 Spring 自动扫描并注册对象。

三、@Bean:把方法返回的对象交给 Spring 管理

@Bean 写在方法上,表示该方法返回的对象需要注册到 Spring 容器中。

java 复制代码
@Bean
public Coffee coffee() {
    return new Coffee();
}

Spring 会调用这个方法,将返回的 Coffee 对象保存为 Bean。其逻辑可以理解为:

text 复制代码
调用 @Bean 方法
→ 得到返回对象
→ 注册到 Spring 容器
→ 后续由 Spring 统一管理

默认情况下,@Bean 方法的方法名就是 Bean 名称。例如:

java 复制代码
@Bean
public Coffee myCoffee() {
    return new Coffee();
}

这个 Bean 的默认名称是 myCoffee。也可以手动指定名称:

java 复制代码
@Bean("coffeeBean")
public Coffee coffee() {
    return new Coffee();
}

此时 Bean 名称为 coffeeBean。

@Bean 常用于注册第三方类,或者需要自定义创建过程的对象。例如 ObjectMapper、DataSource、RedisTemplate 等类通常不是开发者自己编写的,无法直接在源码上添加 @Component,这时就可以使用 @Bean:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.findAndRegisterModules();
        return mapper;
    }
}

四、@Configuration:声明这是一个 Spring 配置类

@Configuration 写在类上,表示这个类是一份 Spring 配置类,里面通常包含一个或多个 @Bean 方法。

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public Coffee coffee() {
        return new Coffee();
    }

    @Bean
    public Tea tea() {
        return new Tea();
    }
}

Spring 启动时发现 @Configuration 后,会读取其中的 Bean 定义并注册对象:

text 复制代码
发现 @Configuration
→ 解析配置类
→ 查找 @Bean 方法
→ 调用方法创建对象
→ 注册到 IoC 容器

可以把 @Configuration 理解成"对象生产规则的集合",而 @Bean 则是其中一条具体的生产规则。

需要注意的是,@Configuration 本身也属于 Spring 组件,因此它也会被注册为 Bean。它与普通 @Component 的重要区别在于,Spring 通常会对完整配置类进行代理增强。

五、@Configuration 的底层代理逻辑

考虑下面的代码:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public User user() {
        return new User();
    }

    @Bean
    public UserService userService() {
        User user1 = user();
        User user2 = user();
        return new UserService(user1, user2);
    }
}

按照普通 Java 方法调用逻辑,user() 被调用两次,似乎应该创建两个不同的 User 对象。但在完整的 @Configuration 模式下,Spring 会对配置类生成代理。

第一次调用 user() 时,Spring 创建对象并放入容器;第二次调用时,代理会优先从容器中查找已有 Bean,而不是再次执行 new User()。因此通常有:

java 复制代码
user1 == user2

这正是 @Configuration 的一个重要作用:保证配置类内部调用 @Bean 方法时,能够返回 Spring 容器中的同一个单例对象。

在较新的 Spring 版本中,可以使用:

java 复制代码
@Configuration(proxyBeanMethods = false)

关闭这种代理行为。关闭后,配置类更接近普通 Java 类,方法之间的直接调用可能会创建新的对象。因此,当 @Bean 方法之间没有直接互相调用时,可以关闭代理以减少开销;如果方法之间存在依赖调用,则需要理解关闭代理后的区别。

六、多个相同类型的 @Bean 会怎样

Spring 允许注册多个类型相同但名称不同的 Bean,例如:

java 复制代码
@Configuration
public class CoffeeConfig {
    @Bean
    public Coffee latteCoffee() {
        return new Coffee("拿铁");
    }

    @Bean
    public Coffee americanoCoffee() {
        return new Coffee("美式");
    }
}

此时容器中有两个 Coffee 类型的 Bean:

Bean 名称 Bean 类型 对象含义
latteCoffee Coffee 拿铁
americanoCoffee Coffee 美式

如果只按类型注入:

java 复制代码
@Autowired
private Coffee coffee;

Spring 会发现有两个候选对象,无法判断应该注入哪一个,通常会抛出 NoUniqueBeanDefinitionException。

1. 使用 @Qualifier 指定名称

java 复制代码
@Autowired
@Qualifier("latteCoffee")
private Coffee coffee;

这表示明确注入名为 latteCoffee 的 Bean。

2. 使用 @Primary 指定默认对象

java 复制代码
@Bean
@Primary
public Coffee latteCoffee() {
    return new Coffee("拿铁");
}

当存在多个同类型 Bean 时,Spring 会优先选择带有 @Primary 的对象。

3. 按名称和类型主动获取

java 复制代码
Coffee coffee = context.getBean("americanoCoffee", Coffee.class);

这种方式同时指定了 Bean 名称和类型。

七、@Component 和 @Bean 的区别

虽然二者最终都能把对象放进 Spring 容器,但它们的使用方式不同。

对比项 @Component @Bean
标注位置 类上 方法上
创建方式 Spring 扫描后自动创建 Spring 调用方法获得对象
适用对象 自己编写的类 第三方类或需要自定义初始化的对象
控制程度 相对简单 可以完全控制创建过程
默认名称 类名首字母小写 方法名

例如,自定义业务类适合使用:

java 复制代码
@Component
public class OrderService {
}

第三方对象更适合使用:

java 复制代码
@Configuration
public class JsonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

八、@Configuration 和普通 @Component 的区别

下面两种写法都可能让 Spring 发现类中的 @Bean 方法:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public Coffee coffee() {
        return new Coffee();
    }
}
java 复制代码
@Component
public class AppConfig {
    @Bean
    public Coffee coffee() {
        return new Coffee();
    }
}

但两者并不完全相同。

@Configuration 表示完整配置类,默认会对 Bean 方法进行代理增强;普通 @Component 中的 @Bean 方法通常属于精简配置模式,不保证类内部直接调用 Bean 方法时一定经过容器代理。

因此,配置类中多个 @Bean 方法存在相互调用时,应优先使用 @Configuration;如果只是简单注册几个互不依赖的对象,普通组件形式也可以工作,但语义上不如 @Configuration 清晰。

九、三个注解的关系

三者可以概括为:

text 复制代码
@Component
作用:让 Spring 自动扫描某个类并创建对象

@Configuration
作用:声明该类是 Spring 配置类,通常用于集中定义 Bean

@Bean
作用:把某个方法返回的对象注册到 Spring 容器

一个典型流程如下:

text 复制代码
Spring 启动
→ 扫描 @Component 和 @Configuration
→ 生成 BeanDefinition
→ 解析 @Bean 方法
→ 实例化对象
→ 完成依赖注入
→ 将对象保存到 IoC 容器

十、完整示例

java 复制代码
public class Coffee {
    private final String name;

    public Coffee(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
java 复制代码
@Component
public class CoffeeShop {
    private final Coffee coffee;

    public CoffeeShop(@Qualifier("latteCoffee") Coffee coffee) {
        this.coffee = coffee;
    }

    public void sell() {
        System.out.println("出售:" + coffee.getName());
    }
}
java 复制代码
@Configuration
public class CoffeeConfig {
    @Bean
    @Primary
    public Coffee americanoCoffee() {
        return new Coffee("美式");
    }

    @Bean
    public Coffee latteCoffee() {
        return new Coffee("拿铁");
    }
}

在这个例子中:

  • CoffeeShop 使用 @Component,由 Spring 自动扫描并创建;
  • CoffeeConfig 使用 @Configuration,表示它是一份配置类;
  • 两个 Coffee 对象通过 @Bean 注册;
  • @Primary 将美式咖啡设置为默认 Bean;
  • CoffeeShop 构造方法中的 @Qualifier("latteCoffee") 又明确指定了拿铁,因此最终注入的是拿铁对象。

十一、常见误区

误区 1:Bean 就是一种特殊的 Java 类

Bean 不是一种新的类,而是一个普通 Java 对象。区别只在于它是否被 Spring 容器管理。

误区 2:使用了 @Bean 就一定要配合 @Configuration

@Bean 方法通常写在 @Configuration 类中,但也可以写在其他被 Spring 管理的组件类中。只是完整配置类和普通组件在代理行为上可能不同。

误区 3:同一种类型只能有一个 Bean

Spring 允许存在多个同类型 Bean,只要名称不同即可。真正发生冲突的是按类型自动注入时无法确定候选对象。

误区 4:Spring 容器只能按类型识别对象

Spring 同时使用 Bean 名称、类型、限定符、主候选标记等信息完成依赖解析,并不是只看类型。

十二、总结

@Component、@Configuration 和 @Bean 都服务于 Spring IoC 容器,但职责不同:

  • @Component:告诉 Spring 自动扫描并创建这个类的对象;
  • @Configuration:告诉 Spring 这是一个配置类,里面保存着对象创建规则;
  • @Bean:告诉 Spring 将该方法返回的对象注册到容器中。

最简洁的记忆方式是:

text 复制代码
@Component:这个类由 Spring 创建
@Configuration:这个类负责配置 Spring
@Bean:这个方法返回的对象由 Spring 管理

理解这三个注解后,就能进一步理解 Spring 的组件扫描、依赖注入、Bean 名称冲突、单例管理以及配置类代理等核心机制。

相关推荐
QianhangQianping11 分钟前
撤销重做不是「存快照」:一个可视化编辑器的Operation 设计
java·前端·数据库·typescript·编辑器·前端架构·撤销重做
海绵宝宝转agent17 分钟前
LeetCodehot100题解
java·笔记·学习·算法·leetcode·深度优先
SunnyDays101118 分钟前
Java 操作 Word 脚注和尾注:添加、读取与删除
java·word
SunnyDays101119 分钟前
Java 为 Word 添加页码:隐藏封面页码、从指定页开始编号、奇偶页页码设置等
java·word·页码
开源推荐官23 分钟前
多门店 O2O 商城选型复盘:从开发视角对比主流开源系统
java·架构·开源·php
吴名氏.24 分钟前
Java转AI Agent教程(第二周)超时重试与错误分类
java·人工智能·python·分类·ai agent
从琳开始9891 小时前
从Socket到UDP回显服务器:Java网络编程完整指南
java·网络·网络协议·udp·java-ee
软件技术NINI1 小时前
基于SpringBoot和Vue的摇滚乐鉴赏网站
vue.js·spring boot·后端·spring·html·毕业设计·毕设
西瓜拿铁好喝2 小时前
开源和闭源 AI 办公工具,分别适合谁
java·前端·数据库
学长毕设2 小时前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端