Spring 中创建 Bean 有几种方式?
结论先行:从"谁把 BeanDefinition 塞进容器"的角度看,常用方式有六类——组件扫描注解、@Bean 工厂方法、@Import 导入、XML <bean>、FactoryBean、以及编程式注册(BeanDefinitionRegistry / Supplier)。前四种是声明式,后两种偏底层;无论哪种,最终都汇成容器里的 BeanDefinition,再统一走实例化、属性填充、初始化。
一、六种方式一览
| 方式 | 典型写法 | Bean 名称来源 | 适用场景 |
|---|---|---|---|
| 组件扫描 | @Component/@Service/@Repository/@Controller | 类名首字母小写,可由 @Component("x") 指定 | 自己写的业务类 |
@Bean 方法 | @Bean public Foo foo() | 方法名,可由 @Bean("x") 指定 | 第三方类、需要手工构造的对象 |
@Import | @Import(Foo.class) | 全限定类名 | 框架内部、开关式导入 |
| XML | <bean id="foo" class="..."> | id 属性 | 老系统、需要外部化配置 |
FactoryBean | 实现 FactoryBean<T> | 容器中登记的是工厂本身 | 复杂对象、代理对象(如 Mapper) |
| 编程式注册 | registry.registerBeanDefinition(...) | 手工指定 | 动态注册、按需生成 Bean |
二、@Import 的三种变体
// 1. 直接导入普通类:当作一个配置类/组件处理
@Import(FooConfig.class)
// 2. ImportSelector:返回要导入的类名数组,可依据注解元数据做开关
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata meta) {
boolean enabled = meta.hasAnnotation("com.demo.EnableCache");
return enabled ? new String[]{"com.demo.CacheConfig"} : new String[0];
}
}
// 3. ImportBeanDefinitionRegistrar:直接操作注册表,可精细控制 BeanDefinition
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
BeanDefinition bd = BeanDefinitionBuilder
.genericBeanDefinition(MyMapper.class)
.addPropertyValue("namespace", "user")
.getBeanDefinition();
registry.registerBeanDefinition("userMapper", bd);
}
}
三、@Bean 与 FactoryBean 的差别
@Configuration
public class BeanConfig {
// 普通 @Bean:返回什么,容器里就是什么
@Bean
public ThreadPoolExecutor executor() {
return new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200));
}
// FactoryBean:容器里 getBean("userDao") 拿到的是 getObject() 的产物
@Bean
public UserDaoFactoryBean userDao() {
return new UserDaoFactoryBean();
}
}
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
@Override
public UserDao getObject() {
return new UserDaoImpl(); // 真实对象在此刻才创建
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
getBean("userDao") → FactoryBean#getObject() 的返回值(UserDao)
getBean("&userDao") → FactoryBean 本身(UserDaoFactoryBean)
四、注册顺序与覆盖规则
- 扫描到的组件先于
@Bean方法注册,@Bean方法之间则按配置类解析顺序注册; - 同名 Bean 默认不允许覆盖(Spring Boot 2.1 起
spring.main.allow-bean-definition-overriding=false),要覆盖需显式打开; @Bean方法之间的依赖用方法参数表达,不要直接调用——直接调用在@Configuration全代理模式下会被拦截返回单例,但代理失效(如@Configuration(proxyBeanMethods = false))时就会变成 new 出新对象。
五、常见追问
- 问:
@Bean方法能是 static 吗? 可以。static 的@Bean方法在配置类实例化之前就能被调用,常用于注册BeanFactoryPostProcessor这类"必须早于其他 Bean"的组件。 - 问:FactoryBean 何时被调用? 在有人第一次
getBean或它被作为依赖注入时,且isSingleton()为 true 时结果会被缓存。 - 问:
@Import与@ComponentScan谁优先? 同一配置类上@Import处理早于扫描,这也是自动配置类常用@Import的原因。 - 问:能用反射
new出来再交给容器吗? 那是registerSingleton(手工单例),只登记实例不登记 BeanDefinition,生命周期回调与 AOP 都不会生效。
小结
六种方式不是为了背名字,而是回答"Bean 从哪来":注解扫出来的、工厂方法造出来的、导入进来的、XML 配出来的、FactoryBean 延迟产出的、编程式塞进去的。答完方式再补一句"都统一为 BeanDefinition 后走同一套实例化流程",答案就闭环了。