Spring 中创建 Bean 有几种方式?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/

面试考察点

面试官提出这个问题,主要想考察以下几个层面的能力:

  1. 对 Spring IoC 核心流程的理解深度:不仅仅是知道有几种方式,更是想知道你是否理解 “声明 Bean” 到 “容器实例化 Bean” 这个核心流程的起点在哪里。
  2. 对多种配置方式的掌握广度与原理认知:你是否了解 Spring 为适应不同场景和演进历史所提供的各种 Bean 定义方式,以及它们背后的实现机制。
  3. 实践中的应用与选型能力:在真实项目中,你能否根据场景(如引入第三方库、复杂对象构造)选择最合适、最优雅的 Bean 创建方式。
  4. 对 Spring 扩展机制的认识:是否了解像 FactoryBean 这样的高级扩展点,这能反映你对框架的钻研程度。

核心答案

在 Spring 中,创建(更准确地说是 “定义” 或 “注册”)Bean 的方式主要有以下五种,它们本质上是向 IoC 容器提供 BeanDefinition 信息的不同途径:

  1. 基于注解声明:使用 @Component 及其衍生注解(如 @Service, @Repository, @Controller),配合 @ComponentScan 进行包扫描。
  2. 基于 XML 配置文件声明:在 XML 文件中使用 <bean> 标签进行定义。
  3. 基于 Java Config 配置类声明:在 @Configuration 标注的配置类中,使用 @Bean 注解方法。
  4. 实现 FactoryBean 接口:通过实现 FactoryBean<T> 接口来定义复杂的、定制化的 Bean 创建逻辑。
  5. 实现特定接口并手动注册:通过实现 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 配置的定位:在遗留项目维护或需要完全不依赖代码编译的灵活配置时使用,新项目已不推荐。
  • 手动注册:主要用于框架级别的深度扩展,日常业务开发极少使用。

常见误区

  1. 混淆 @Component@Bean@Component 标注在上,由 Spring 扫描发现;@Bean 标注在配置类的方法上,你自己控制方法的实现并返回对象。简单来说,用 @Component 是你告诉 Spring “这是我的类,请管理它”;用 @Bean 是你交给 Spring 一个已经组装好的对象,说“请管理这个对象”。
  2. 认为只有 @Component 和 XML 两种方式:这反映出对 Spring 的认知还停留在基础层面,不了解 Java Config 和扩展机制。
  3. 过度使用 XML:在基于 Spring Boot 的新项目中,纯 XML 配置已非主流,应优先使用基于 Java 的配置。

总结

Spring 提供了从便捷注解到高度可编程的多种 Bean 定义方式,其核心思想都是向容器提供 BeanDefinition;在实际开发中,应根据 “是否控制源码”、“对象构造复杂度”、“配置灵活性” 等需求,灵活选用 @Component@BeanFactoryBean 等最合适的方式。