Spring 中创建 Bean 有几种方式?
2026年01月29日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官提出这个问题,主要想考察以下几个层面的能力:
- 对 Spring IoC 核心流程的理解深度:不仅仅是知道有几种方式,更是想知道你是否理解 “声明 Bean” 到 “容器实例化 Bean” 这个核心流程的起点在哪里。
- 对多种配置方式的掌握广度与原理认知:你是否了解 Spring 为适应不同场景和演进历史所提供的各种 Bean 定义方式,以及它们背后的实现机制。
- 实践中的应用与选型能力:在真实项目中,你能否根据场景(如引入第三方库、复杂对象构造)选择最合适、最优雅的 Bean 创建方式。
- 对 Spring 扩展机制的认识:是否了解像
FactoryBean这样的高级扩展点,这能反映你对框架的钻研程度。
核心答案
在 Spring 中,创建(更准确地说是 “定义” 或 “注册”)Bean 的方式主要有以下五种,它们本质上是向 IoC 容器提供 BeanDefinition 信息的不同途径:
- 基于注解声明:使用
@Component及其衍生注解(如@Service,@Repository,@Controller),配合@ComponentScan进行包扫描。 - 基于 XML 配置文件声明:在 XML 文件中使用
<bean>标签进行定义。 - 基于 Java Config 配置类声明:在
@Configuration标注的配置类中,使用@Bean注解方法。 - 实现
FactoryBean接口:通过实现FactoryBean<T>接口来定义复杂的、定制化的 Bean 创建逻辑。 - 实现特定接口并手动注册:通过实现
BeanDefinitionRegistryPostProcessor等接口,在运行时以编程方式动态注册BeanDefinition。
深度解析
原理/机制
所有方式的核心目标都是向 Spring 的 BeanDefinitionRegistry(如 DefaultListableBeanFactory)中注册一个 BeanDefinition 对象。这个对象是 Bean 的 “配方” 或 “蓝图”,包含了 Bean 的类名、作用域、初始化方法、属性值等所有元数据。容器在后续的实例化阶段,会根据这份 “蓝图” 通过反射等机制来创建具体的 Bean 实例。
代码示例
1. 注解声明
@Service // 本质上是一个 @Component
public class UserServiceImpl implements UserService {
// ...
}
@Configuration
@ComponentScan("com.example.service") // 扫描指定包下的注解
public class AppConfig {
}
2. XML 声明
<!-- applicationContext.xml -->
<beans>
<bean id="userService" class="com.example.service.UserServiceImpl"/>
</beans>
3. Java Config 配置类声明
@Configuration
public class AppConfig {
@Bean // 方法的返回值会被注册为 Bean,方法名默认作为 Bean 的 name
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/test");
// ... 设置其他属性
return ds;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
// 演示方法注入,Spring 会自动传入上面定义的 dataSource Bean
return new DataSourceTransactionManager(dataSource);
}
}
4. 实现 FactoryBean 接口
public class MyFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
// 这里是复杂的创建逻辑,可能来自远程调用、动态代理等
User user = new User();
user.setName("FactoryBean Created");
return user;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
// 使用时,容器中实际存在两个 Bean:`&myFactoryBean` (FactoryBean 本身) 和 `myFactoryBean` (getObject() 的返回值)
5. 实现特定接口手动注册
@Component
public class MyBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 动态构造一个 BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyDynamicBean.class);
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
// 手动注册到容器
registry.registerBeanDefinition("myDynamicBean", beanDefinition);
}
}
最佳实践与对比分析
- 现代 Spring 应用首选
@Component扫描和@Configuration+@Bean:它们类型安全、可读性强、与 Java 代码结合紧密,是响应式、Spring Boot 等现代开发模式的基石。 @Bean方法的强大之处:它非常适合集成无法修改源码的第三方库(例如上述DataSource的创建),或者需要显式控制构造过程的复杂 Bean(例如构建一个需要特定参数的 HttpClient)。FactoryBean的应用场景:当 Bean 的创建过程极其复杂(例如集成 MyBatis 的SqlSessionFactoryBean,它需要构建大量内部对象),或者你想对某个接口的所有实现类进行统一代理/增强时,FactoryBean是绝佳的武器。- XML 配置的定位:在遗留项目维护或需要完全不依赖代码编译的灵活配置时使用,新项目已不推荐。
- 手动注册:主要用于框架级别的深度扩展,日常业务开发极少使用。
常见误区
- 混淆
@Component和@Bean:@Component标注在类上,由 Spring 扫描发现;@Bean标注在配置类的方法上,你自己控制方法的实现并返回对象。简单来说,用@Component是你告诉 Spring “这是我的类,请管理它”;用@Bean是你交给 Spring 一个已经组装好的对象,说“请管理这个对象”。 - 认为只有
@Component和 XML 两种方式:这反映出对 Spring 的认知还停留在基础层面,不了解 Java Config 和扩展机制。 - 过度使用 XML:在基于 Spring Boot 的新项目中,纯 XML 配置已非主流,应优先使用基于 Java 的配置。
总结
Spring 提供了从便捷注解到高度可编程的多种 Bean 定义方式,其核心思想都是向容器提供 BeanDefinition;在实际开发中,应根据 “是否控制源码”、“对象构造复杂度”、“配置灵活性” 等需求,灵活选用 @Component、@Bean 或 FactoryBean 等最合适的方式。