一、面试现场:从一道题讲起
“Spring 容器实例化 Bean 的方法有哪些?”这是字节跳动后端面试中一道高频基础题。不少候选人只答出“无参构造 + @Bean”就停了,面试官继续追问“那 FactoryBean 和实例工厂的区别是什么?底层调用链怎么走?”往往就卡住了。真正能拿下 37K 的答案,不能只背八股,还需要把配置方式、注解驱动、源码调用链完整串起来。本文按面试现场的节奏,把这道题拆成“基础回答—进阶追问—源码验证”三个层次,帮你形成一套可以拿高分的表达。
在正式展开之前,先说明本文范围:只讨论 Spring 核心容器中的 Bean 实例化,不涉及 Spring Boot 自动装配的展开细节;源码以 Spring Framework 5.3.x 为例,6.x 原理相同。
二、先理清概念:Bean 实例化到底是什么
很多人把“实例化”和“初始化”混为一谈,面试时一开口就容易露怯。我们先把这两个词讲清楚。
2.1 BeanDefinition 是图纸,实例化是照着图纸造对象
Spring 容器启动时,会读取 XML、注解或 Java 配置,把每个 Bean 的描述信息封装成BeanDefinition。BeanDefinition 里保存了类名、作用域、构造参数、属性、初始化方法、销毁方法等元数据,但它本身并不是对象。所谓“实例化 Bean”,就是 Spring 根据 BeanDefinition 中的元数据,真正创建一个 Java 对象的过程。
可以用一个比喻:BeanDefinition 相当于建筑图纸,实例化相当于施工队按照图纸把房子盖出来。房子盖好之后,再进行内部装修,也就是“属性填充”和“初始化”。
2.2 实例化与初始化的区别
这是面试中非常容易被追问的点。Spring 创建 Bean 的完整流程通常可以概括为三步:
- 实例化(Instantiation):调用构造器或工厂方法创建对象实例,此时对象的属性还未填充。
- 属性填充(Populate):根据 BeanDefinition 中的属性信息,通过反射调用 setter 方法或字段注入,把依赖对象赋值进去。
- 初始化(Initialization):执行 Aware 接口回调、@PostConstruct 方法、InitializingBean 的 afterPropertiesSet、自定义 init-method 等逻辑。
实例化只是第一步。我们可以用一张表对比:
| 阶段 | 核心动作 | 典型入口 |
|---|---|---|
| 实例化 | 创建对象实例,不填充属性 | createBeanInstance() |
| 属性填充 | 注入依赖属性 | populateBean() |
| 初始化 | 执行回调与自定义初始化逻辑 | initializeBean() |
如果面试官让你画出 Bean 的生命周期,把这三个阶段分清楚,是一个重要的加分项。
三、Spring 中实例化 Bean 的 4 类方法
回到面试题本身。可以从“配置方式”和“底层策略”两个维度来组织答案。先说配置方式,一共有以下四类常见手段。
3.1 构造器实例化(默认方式)
最基础的方式,通过类的构造器创建对象。默认使用无参构造器,也可以通过配置指定有参构造器。在 XML 中不指定工厂方法时,Spring 默认走构造器实例化。
示例:定义一个普通的 UserService 类。
public class UserService { private UserDao userDao; public UserService() { } public UserService(UserDao userDao) { this.userDao = userDao; } }XML 配置如下:
<bean id="userService" class="com.example.UserService"> <constructor-arg ref="userDao"/> </bean>如果不写 constructor-arg,Spring 会尝试查找无参构造器。如果类没有无参构造器,只有有参构造器,且没有通过配置告知 Spring 如何传参,启动时就会抛出 BeanCreationException。
注解方式更简单:类上标注 @Component,构造器上的 @Autowired 可以省略(当类只有一个构造器时,Spring 会自动用它注入)。
@Component public class UserService { private final UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } }3.2 静态工厂方法实例化
有些对象的创建逻辑比较复杂,或者需要复用已有静态工厂方法,这时可以让 Spring 调用类的静态方法来创建 Bean。例如一个静态工厂类:
public class CarFactory { public static Car createCar() { return new Car(); } }XML 配置使用 factory-method 属性:
<bean id="car" class="com.example.CarFactory" factory-method="createCar"/>注意:class 指向的是工厂类,而不是最终返回的 Car。Spring 会调用 CarFactory.createCar() 静态方法,把返回值注册为 id 为 car 的 Bean。
注解方式中,可以在 @Bean 方法上直接返回对象,原理类似。
3.3 实例工厂方法实例化
静态工厂要求创建方法属于类本身,而实例工厂要求先创建工厂对象,再调用工厂对象的实例方法。因此需要配置两个 Bean:工厂 Bean 和目标 Bean。
public class CarFactory { public Car createCar() { return new Car(); } }XML 配置:
<bean id="carFactory" class="com.example.CarFactory"/> <bean id="car" factory-bean="carFactory" factory-method="createCar"/>与静态工厂的区别是:静态工厂用 class + factory-method,实例工厂用 factory-bean + factory-method。这点经常被问到,需要说清楚。
3.4 FactoryBean 接口
FactoryBean 是 Spring 提供的一种特殊 Bean。它本身会被 Spring 注册,但 getBean("xxx") 返回的不是 FactoryBean 实例,而是它 getObject() 方法返回的对象。如果要获取 FactoryBean 本身,需要在 name 前加&符号。
自定义 FactoryBean 示例:
@Component public class CarFactoryBean implements FactoryBean<Car> { @Override public Car getObject() throws Exception { return new Car(); } @Override public Class<?> getObjectType() { return Car.class; } @Override public boolean isSingleton() { return true; } }当 getBean("carFactoryBean") 时,实际获取的是 Car 对象。如果 Spring 内部需要获取 FactoryBean 本身,例如处理依赖注入、生命周期回调时,会先拿到 FactoryBean 实例。
FactoryBean 与实例工厂方法的本质区别在于:FactoryBean 本身就是一个 BeanDefinition,Spring 会先实例化 FactoryBean,再调用 getObject() 获取目标对象;而实例工厂方法需要额外配置一个工厂 Bean 和 factory-method,工厂 Bean 和目标 Bean 是两个独立的 BeanDefinition。
3.5 @Bean 与 @Component 注解方式
现代 Spring 应用几乎都在使用注解驱动。@Component、@Service、@Repository、@Controller 等注解标记的类,会被组件扫描发现并注册为 BeanDefinition,之后同样需要实例化。@Bean 则用在 @Configuration 或 @Component 类的方法上,表示方法返回值是一个 Bean。
@Configuration public class AppConfig { @Bean public DataSource dataSource() { return new HikariDataSource(); } }@Bean 实例化的底层,Spring 会通过反射调用该方法,把返回值作为 Bean。这里还涉及@Configuration 的 CGLIB 代理:当 @Configuration 类被代理后,同一个 @Bean 方法被多次调用时,不会重复创建对象,而是从容器缓存中获取已存在的单例 Bean。这个点后面追问部分会展开。
到这里,最常问的四类方法就回答完了。但面试官往往不是只听你报名字,而是要听源码。
四、源码层面:从 getBean 到 createBeanInstance
这一节是拉开差距的关键。面试时可以说:“从容器获取 Bean 时,最终会进入 AbstractAutowireCapableBeanFactory 的 createBeanInstance 方法,它会根据 BeanDefinition、构造器推断结果和实例化策略,选择构造器反射或 CGLIB 来创建实例。”
完整调用链大致如下:
AbstractApplicationContext.refresh() -> finishBeanFactoryInitialization() -> preInstantiateSingletons() -> getBean() -> doGetBean() -> createBean() -> doCreateBean() -> createBeanInstance()4.1 确定构造器:determineConstructorsFromBeanPostProcessors
在 createBeanInstance 中,Spring 首先会检查 BeanDefinition 中是否配置了 Supplier、工厂方法或 factory-method。如果都没有,就进入构造器推断逻辑。
Spring 会遍历所有 SmartInstantiationAwareBeanPostProcessor,其中最关键的是AutowiredAnnotationBeanPostProcessor,它负责处理 @Autowired 构造器。默认情况下,@Autowired 标注的构造器优先被选中;如果类只有一个构造器,即使没有 @Autowired 也会被选中。
确定构造器后,Spring 还可能通过ConstructorResolver解析构造器参数,处理参数名称、@Value 注解、循环依赖等情况。
4.2 两种实例化策略
Spring 提供了两种实例化策略,接口为InstantiationStrategy:
- SimpleInstantiationStrategy:基于 JDK 反射,调用 Constructor.newInstance() 创建对象。
- CglibSubclassingInstantiationStrategy:继承 SimpleInstantiationStrategy,当需要方法注入(Lookup Method Injection、Replace Method)时,使用 CGLIB 动态生成子类,否则仍走反射。
默认策略是 CglibSubclassingInstantiationStrategy。实例化 Bean 时,如果没有特殊需求,最终会调用 BeanUtils.instantiateClass(),通过反射执行构造器。
4.3 实例化策略的底层:JDK 反射与 CGLIB
普通情况下,Spring 优先使用 JDK 反射创建对象,性能较好;只有检测到 BeanDefinition 中定义了 MethodOverride(例如 lookup-method、replaced-method),才使用 CGLIB 生成子类,因为此时需要在子类中覆盖方法实现。
创建对象的入口代码可以简化为:
// 简化的 createBeanInstance 判断逻辑 if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (factoryMethodName != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) { return autowireConstructor(beanName, mbd, ctors, args); } return instantiateBean(beanName, mbd);注意:这是源码逻辑的简化表达,实际代码有更多判断。面试时只要能准确说出这个判断顺序,就已经超过了大多数候选人。
五、完整可运行示例
下面给出一个同时演示构造器、静态工厂、实例工厂和 FactoryBean 的 Spring Boot 项目示例,方便读者本地运行验证。
项目结构:
com.example.demo ├── DemoApplication.java ├── config/AppConfig.java ├── bean/Car.java ├── bean/UserDao.java ├── bean/UserService.java ├── factory/CarFactory.java ├── factory/CarFactoryBean.java └── runner/BeanRunner.javaCar 实体类:
public class Car { private String brand = "BYD"; public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } }静态工厂与实例工厂:
public class CarFactory { public static Car createStaticCar() { return new Car(); } public Car createInstanceCar() { return new Car(); } }FactoryBean 实现:
public class CarFactoryBean implements FactoryBean<Car> { @Override public Car getObject() throws Exception { return new Car(); } @Override public Class<?> getObjectType() { return Car.class; } }配置类,演示多种注册方式:
@Configuration public class AppConfig { // 1. 构造器实例化:通过 @Component 扫描 // 2. @Bean 方式 @Bean public Car beanStyleCar() { return new Car(); } // 3. 静态工厂 @Bean public Car staticFactoryCar() { return CarFactory.createStaticCar(); } // 4. 实例工厂:先注册工厂 Bean,再调用方法 @Bean public CarFactory carFactory() { return new CarFactory(); } @Bean public Car instanceFactoryCar(CarFactory carFactory) { return carFactory.createInstanceCar(); } // 5. FactoryBean @Bean public CarFactoryBean carFactoryBean() { return new CarFactoryBean(); } }测试运行:
@Component public class BeanRunner implements CommandLineRunner { @Autowired private ApplicationContext context; @Override public void run(String... args) { System.out.println(context.getBean("beanStyleCar").getClass()); System.out.println(context.getBean("staticFactoryCar").getClass()); System.out.println(context.getBean("instanceFactoryCar").getClass()); System.out.println(context.getBean("carFactoryBean").getClass()); System.out.println(context.getBean("&carFactoryBean").getClass()); } }输出显示,前四个对象都是 Car 类型,最后一个以&开头获取到的是 CarFactoryBean 本身。这个例子能很直观地验证 FactoryBean 的取 Bean 规则。
六、面试高频追问与回答模板
这道题往往不会只停留在“有哪些方法”,面试官会继续往下挖掘。下面整理几个高频追问。
6.1 构造器注入和 Setter 注入怎么选?
回答要点:优先使用构造器注入。
- 构造器注入能保证依赖不可变,字段可以用 final 修饰,对象创建完成即可用,依赖更明确。
- Setter 注入适合可选依赖或可能出现循环依赖的场景,但会暴露可变状态。
- Spring 官方推荐:强制依赖用构造器注入,可选依赖用 Setter 注入。
6.2 循环依赖时 Spring 如何实例化 Bean?
循环依赖发生在属性填充阶段,而不是实例化阶段。Spring 解决单例循环依赖依赖三级缓存:
- 一级缓存 singletonObjects:存放完全初始化好的 Bean。
- 二级缓存 earlySingletonObjects:存放提前暴露的 Bean 实例,尚未填充属性。
- 三级缓存 singletonFactories:存放 ObjectFactory,用于生成早期引用。
实例化完成但还未填充属性时,Spring 会把当前 Bean 包装成 ObjectFactory 放入三级缓存。当 B 依赖 A 时,可以从三级缓存获取 A 的早期引用,从而打破循环。需要注意的是,构造器注入的循环依赖无法用三级缓存解决,因为构造器阶段还没完成实例化,无法提前暴露。这也是推荐构造器注入但也要注意循环依赖的原因。
6.3 FactoryBean 和 BeanFactory 的区别
这个问题专门用来考察概念是否扎实。两者虽然名字相近,但定位完全不同。
- BeanFactory:是 Spring 容器的顶层接口,负责管理 Bean 的创建、获取、依赖注入和生命周期。
- FactoryBean:是一种特殊的 Bean,本身也会注册到容器中,但它对外暴露的是 getObject() 返回的对象。
简单记:BeanFactory 是“容器”,FactoryBean 是“能生产对象的工厂 Bean”。当面试官问“二者区别”时,先明确一个是基础设施,一个是特殊 Bean,再补一句“获取 FactoryBean 本身要加&前缀”,会显得理解很深。
6.4 @Bean 和 @Component 的区别
这是注解驱动场景下的高频追问,回答时可以从作用对象和产生方式两个角度切入。
- 作用对象不同:@Component 标注类,通过类路径扫描注册 Bean;@Bean 标注方法,通过方法返回值注册 Bean。
- 适用场景不同:自己写的类、能直接通过组件扫描管理的,优先用 @Component;引入第三方库对象、需要自定义创建逻辑或统一管理多态实例时,用 @Bean。
- 底层行为:@Bean 方法在 @Configuration 代理类中,Spring 会拦截重复调用,保证单例 Bean 只创建一次。
可以总结成一句话:@Component 是“扫描类生成 Bean”,@Bean 是“调用方法生成 Bean”。
七、总结:如何把这道题答出高分
把整道题串起来,面试时可以用下面的顺序组织回答:
- 先给定义:先说 Bean 实例化是 Spring 根据 BeanDefinition 创建对象实例的过程,并区分实例化和初始化。
- 再列方法:从构造器实例化、静态工厂、实例工厂、FactoryBean 到 @Bean/@Component,把配置方式和注解驱动都点到。
- 最后讲源码:说明调用入口 createBeanInstance()、构造器推断、两种实例化策略,以及 JDK 反射与 CGLIB 的选择依据。
- 补充追问:主动提到循环依赖、三级缓存、FactoryBean 与 BeanFactory 的区别,展示知识深度。
这样回答既有广度又有深度,不会停留在八股背诵。建议本地把第五章示例跑一遍,再把第四章调用链画一遍,这样面试时表达会更自然、更经得起追问。