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 常用于注册第三方类,或者需要自定义创建过程的对象。例如 ObjectMapperDataSourceRedisTemplate 等类通常不是开发者自己编写的,无法直接在源码上添加 @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 名称冲突、单例管理以及配置类代理等核心机制。

相关推荐
IvorySQL1 小时前
云环境下PostgreSQL的Cgroup内存管理实践
java·数据库·postgresql
不定积分要+C_yyy1 小时前
Java基础核心精讲:基本数据类型与包装类区别、自动装箱拆箱与缓存陷阱实战解析
java·开发语言
jvmind_dev1 小时前
第三章:GC 日志深度分析(上)——多版本、多收集器的统一解析
java·agent
xingxiliang1 小时前
ReliableAgent:类似工程级可用的agent示例
java·开发语言·agent
亦暖筑序1 小时前
AgentScope-Java 入门:保存评审状态并生成 Markdown 报告
java·人工智能·后端
invicinble2 小时前
关于微服务基本框架搭建--fegin最小使用demo
java·spring boot·微服务
梦想的旅途22 小时前
企业微信API 实现企业微信消息的自动发送?
java·服务器·前端
大不点wow2 小时前
为什么 Spring 更推荐构造器注入,而不是字段 `@Autowired`
java·spring·intellij-idea
用户298698530142 小时前
Java 创建 CSV 文件的三种实用方法:从零构建、数组写入与 Excel 转换
java·后端·excel