Spring 中创建 Bean 有几种方式?

#Java#面试八股

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 后走同一套实例化流程",答案就闭环了。

笔记加载中…