Spring 中创建 Bean 有几种方式?
面试考察点
-
全面性:面试官不仅仅是想知道 "注解 + XML" 两种方式,更是想看你能否系统地列出所有注册 Bean 的方式,包括
@Bean、@Component、FactoryBean、@Import等容易遗漏的方式。 -
原理深度:能否讲清楚每种方式底层走的是哪条创建路径(反射、工厂方法、代理),以及它们的执行时机。
-
实战区分:能否说清楚
@Bean和@Component的区别,FactoryBean和BeanFactory的区别。这两个对比追问几乎是标配。
核心答案
先说结论: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 的名称(restTemplate、objectMapper)。方法参数会自动从容器中注入。
适用场景:第三方库的类、需要复杂初始化逻辑的 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(类) |
| 工厂方法 | 调用方法的返回值 | @Bean、FactoryBean.getObject() |
| 代理对象 | CGLIB / JDK 动态代理 | AOP 代理、ProxyFactoryBean |
不管是哪种方式,最终都绕不开这三种路径。Spring 拿到 BeanDefinition 后,根据定义决定用哪种方式实例化。
面试高频追问
-
@Bean和@Component有什么区别?@Component标在类上,适用于自己的代码;@Bean标在方法上,适用于第三方库。@Component通过反射调构造方法创建,@Bean通过调用方法返回值创建。
-
FactoryBean和BeanFactory有什么区别?BeanFactory是 Spring IoC 容器的顶层接口,负责管理和获取所有 Bean。FactoryBean是一个特殊的 Bean,用来创建另一个复杂对象。一个是 "Bean 工厂",一个是 "工厂 Bean",名字正好反过来。
-
@Import有哪几种用法?- 三种:直接导入配置类、直接导入普通类(注册为 Bean)、配合
ImportSelector动态选择导入。
- 三种:直接导入配置类、直接导入普通类(注册为 Bean)、配合
-
Spring 的
@ComponentScan能扫描到第三方的 Bean 吗?- 默认不能,只扫描当前包及子包。但可以通过
@ComponentScan(basePackages = "com.thirdparty")指定第三方包路径来扫描。不过通常第三方库的类不会标注@Component,所以还是用@Bean注册更常见。
- 默认不能,只扫描当前包及子包。但可以通过
常见面试变体
- "
@Bean和@Component的区别?" - "
FactoryBean和BeanFactory的区别?" - "如何向 Spring 容器注册一个第三方类的对象?"
- "SpringBoot 的自动配置用了哪种注册 Bean 的方式?"
记忆口诀
六种方式:组件扫描(@Component)、配置类(@Bean)、工厂 Bean(FactoryBean)、手动导入(@Import)、XML、编程式。
选择依据:自己的类用 @Component,第三方库用 @Bean,复杂逻辑用 FactoryBean。
FactoryBean vs BeanFactory:一个是 "工厂 Bean"(创建复杂对象),一个是 "Bean 工厂"(管理所有 Bean)。
总结
Spring 创建 Bean 的方式虽然多,但核心思路就两类:标注注解让 Spring 自动发现(@Component)和手动告诉 Spring 怎么创建(@Bean、FactoryBean、@Import)。面试时先把六种方式列出来,重点展开 @Bean vs @Component 和 FactoryBean vs BeanFactory 这两组对比,面试官基本就满意了。
