Spring 中创建 Bean 有几种方式?


面试考察点

  1. 全面性:面试官不仅仅是想知道 "注解 + XML" 两种方式,更是想看你能否系统地列出所有注册 Bean 的方式,包括 @Bean@ComponentFactoryBean@Import 等容易遗漏的方式。

  2. 原理深度:能否讲清楚每种方式底层走的是哪条创建路径(反射、工厂方法、代理),以及它们的执行时机。

  3. 实战区分:能否说清楚 @Bean@Component 的区别,FactoryBeanBeanFactory 的区别。这两个对比追问几乎是标配。

核心答案

先说结论:Spring 中注册 Bean 到容器主要有 6 种方式

方式关键字/注解适用场景常用程度
组件扫描@Component / @Service / @Repository / @Controller自己写的类,直接标注⭐⭐⭐⭐⭐
配置类方法@Configuration + @Bean第三方库的对象⭐⭐⭐⭐⭐
FactoryBean实现 FactoryBean<T> 接口创建逻辑复杂的 Bean⭐⭐⭐
@Import@Import / ImportSelector / ImportBeanDefinitionRegistrar动态/条件注册 Bean⭐⭐⭐
XML 配置<bean> 标签老项目维护⭐⭐
编程式注册BeanDefinitionRegistry框架开发、动态注册⭐⭐

实际开发中最常用的是前两种:@Component 系列注解用于自己的类,@Bean 用于第三方库。

深度解析

一、@Component 系列注解(组件扫描)

这是最常见的方式。SpringBoot 启动时会自动扫描 @SpringBootApplication 所在包及其子包下所有标注了 @Component(及其衍生注解)的类,自动注册为 Bean。

@Service
public class UserService {
    // 自动注册为 Bean,beanName 默认为 "userService"
}

@Repository
public class UserDao {
    // @Repository 也能注册,语义上区分数据访问层
}

@Controller
public class UserController {
    // @Controller 用于 Web 层
}

@Component
public class MyUtils {
    // 通用组件
}

@Service@Repository@Controller 本质上就是 @Component 的别名,功能完全一样,只是语义不同。面试官有时会追问 "这四个注解有什么区别",答案就是:运行时没有区别,纯粹是语义区分

适用场景:你自己写的类,源码在你手里,想加注解就加。

二、@Configuration + @Bean

这种方式用于第三方库的对象注册。比如你要往容器里放一个 RestTemplate,这个类是 Spring 框架提供的,你没法在它的源码上加 @Component,那就用 @Bean 来注册。

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }

    // 方法参数自动注入
    @Bean
    public DataSource dataSource() {
        // Spring 会自动从容器中找到对应类型的 Bean 注入参数
        return new HikariDataSource();
    }
}

@Bean 方法返回的对象会被 Spring 容器管理。方法名就是 Bean 的名称(restTemplateobjectMapper)。方法参数会自动从容器中注入。

适用场景:第三方库的类、需要复杂初始化逻辑的 Bean

面试常问:@Bean@Component 有什么区别?

对比项@Component@Bean
标注位置标在类上标在方法上
适用对象自己写的类第三方库的类
创建方式Spring 通过反射调用构造方法调用 @Bean 方法的返回值
初始化逻辑简单(构造方法)灵活(方法里写任意逻辑)

三、FactoryBean

FactoryBean 是 Spring 提供的一个特殊接口,适用于创建逻辑复杂的 Bean。它本身是一个 Bean,但它 "生产" 出来的对象才是真正注册到容器中的 Bean。

public class MyFactoryBean implements FactoryBean<UserService> {

    @Override
    public UserService getObject() {
        // 复杂的创建逻辑
        UserService service = new UserService();
        service.init(); // 初始化操作
        return service;
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }

    @Override
    public boolean isSingleton() {
        return true; // 单例
    }
}

// 注册
@Configuration
public class Config {
    @Bean
    public MyFactoryBean myFactoryBean() {
        return new MyFactoryBean();
    }
}

// 使用:注入的是 getObject() 返回的 UserService,不是 MyFactoryBean 本身
@Autowired
private UserService userService;

关键理解:容器中注册了 MyFactoryBean,但 getBean("myFactoryBean") 拿到的是 UserService 对象。如果想拿到 FactoryBean 本身,要用 getBean("&myFactoryBean")(加 & 前缀)。

FactoryBean 在框架中大量使用:

  • MyBatis 的 SqlSessionFactoryBean
  • Spring AOP 的 ProxyFactoryBean
  • Hibernate 的 LocalSessionFactoryBean

适用场景:创建逻辑复杂、需要条件判断或额外初始化步骤的 Bean。

四、@Import

@Import 用于手动导入 Bean,比组件扫描更精确,支持三种用法:

// 用法 1:直接导入一个配置类
@Import(RedisConfig.class)
public class MyApp {
}

// 用法 2:导入一个普通类(直接注册为 Bean)
@Import(UserService.class)
public class MyApp {
}

// 用法 3:配合 ImportSelector,动态选择导入哪些配置
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // 根据条件返回要导入的配置类全限定名
        return new String[]{"com.example.RedisConfig"};
    }
}

@Import(MyImportSelector.class)
public class MyApp {
}

SpringBoot 的自动配置大量使用了 @Import + ImportSelector@EnableAutoConfiguration 就是通过 ImportSelector 加载 spring.factories 中列出的自动配置类。

适用场景:需要精确控制 Bean 导入、动态条件注册。

五、XML 配置

老项目可能还在用,新项目基本不用了。但面试偶尔会问。

<!-- 通过构造方法创建 -->
<bean id="userService" class="com.example.UserService"/>

<!-- 通过工厂方法创建 -->
<bean id="userService" factory-bean="myFactory" factory-method="create"/>

六、编程式注册

直接操作 BeanDefinitionRegistry,在运行时动态注册 Bean。这种用法比较高级,一般框架开发者用得比较多。

@Component
public class MyBeanRegistrar implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        RootBeanDefinition definition = new RootBeanDefinition(UserService.class);
        registry.registerBeanDefinition("userService", definition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
    }
}

适用场景:框架开发、运行时动态注册 Bean、MyBatis Mapper 扫描等。

七、创建方式的本质对比

面试官可能会追问:这些方式底层到底是怎么创建对象的?本质上只有三种创建路径:

创建路径底层机制对应方式
反射调用构造方法Constructor.newInstance()@Component<bean class="...">@Import(类)
工厂方法调用方法的返回值@BeanFactoryBean.getObject()
代理对象CGLIB / JDK 动态代理AOP 代理、ProxyFactoryBean

不管是哪种方式,最终都绕不开这三种路径。Spring 拿到 BeanDefinition 后,根据定义决定用哪种方式实例化。

面试高频追问

  1. @Bean@Component 有什么区别?

    • @Component 标在类上,适用于自己的代码;@Bean 标在方法上,适用于第三方库。@Component 通过反射调构造方法创建,@Bean 通过调用方法返回值创建。
  2. FactoryBeanBeanFactory 有什么区别?

    • BeanFactory 是 Spring IoC 容器的顶层接口,负责管理和获取所有 Bean。FactoryBean 是一个特殊的 Bean,用来创建另一个复杂对象。一个是 "Bean 工厂",一个是 "工厂 Bean",名字正好反过来。
  3. @Import 有哪几种用法?

    • 三种:直接导入配置类、直接导入普通类(注册为 Bean)、配合 ImportSelector 动态选择导入。
  4. Spring 的 @ComponentScan 能扫描到第三方的 Bean 吗?

    • 默认不能,只扫描当前包及子包。但可以通过 @ComponentScan(basePackages = "com.thirdparty") 指定第三方包路径来扫描。不过通常第三方库的类不会标注 @Component,所以还是用 @Bean 注册更常见。

常见面试变体

  • "@Bean@Component 的区别?"
  • "FactoryBeanBeanFactory 的区别?"
  • "如何向 Spring 容器注册一个第三方类的对象?"
  • "SpringBoot 的自动配置用了哪种注册 Bean 的方式?"

记忆口诀

六种方式:组件扫描(@Component)、配置类(@Bean)、工厂 Bean(FactoryBean)、手动导入(@Import)、XML、编程式。

选择依据:自己的类用 @Component,第三方库用 @Bean,复杂逻辑用 FactoryBean

FactoryBean vs BeanFactory:一个是 "工厂 Bean"(创建复杂对象),一个是 "Bean 工厂"(管理所有 Bean)。

总结

Spring 创建 Bean 的方式虽然多,但核心思路就两类:标注注解让 Spring 自动发现@Component)和手动告诉 Spring 怎么创建@BeanFactoryBean@Import)。面试时先把六种方式列出来,重点展开 @Bean vs @ComponentFactoryBean vs BeanFactory 这两组对比,面试官基本就满意了。