Spring 底层如何创建对象:反射机制与实例化策略
一、核心结论
默认情况下,Spring 通过反射调用无参构造方法来创建 Bean 实例。 但这只是最常见的情况。Spring 的实例化策略比这更丰富,具体取决于 Bean 的定义方式和配置。
二、Spring 实例化的完整流程
Spring 容器在 refresh() 阶段会触发 preInstantiateSingletons(),对所有非懒加载的单例 Bean 进行实例化。核心入口在 AbstractAutowireCapableBeanFactory.createBeanInstance() 方法中。
简化后的流程:
AbstractApplicationContext.refresh()
└── finishBeanFactoryInitialization()
└── preInstantiateSingletons()
└── getBean() → doGetBean() → createBean()
└── doCreateBean()
└── createBeanInstance()
├── 1. 如果使用 Supplier → 调用 Supplier.get()
├── 2. 如果使用工厂方法 → 调用工厂方法
├── 3. 如果有多个构造方法 → 自动注入构造方法
└── 4. 否则 → 使用无参构造方法 + 反射
三、三种实例化方式
3.1 无参构造方法 + 反射(默认方式)
这是最常见的场景。当 <bean> 配置中没有指定构造参数,且类有默认无参构造方法时,Spring 使用 SimpleInstantiationStrategy 通过反射调用无参构造方法。
java
// 源码逻辑简化
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
Constructor<?> constructorToUse = bd.getResolvedConstructorOrFactoryMethod();
if (constructorToUse == null) {
Class<?> clazz = bd.getBeanClass();
// 获取无参构造方法
constructorToUse = clazz.getDeclaredConstructor();
bd.setResolvedConstructorOrFactoryMethod(constructorToUse);
}
// 通过反射调用构造方法
return BeanUtils.instantiateClass(constructorToUse);
}
BeanUtils.instantiateClass() 内部:
java
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) {
ReflectionUtils.makeAccessible(ctor); // 设置构造方法可访问
return ctor.newInstance(args); // 反射调用
}
这就是最常见的"通过反射调用无参构造方法创建对象"。
3.2 有参构造方法 + 自动注入
如果 Bean 类没有无参构造方法,但有一个有参构造方法,Spring 会自动使用该构造方法并注入参数(构造器注入)。
java
@Component
public class UserService {
private final UserRepository userRepository;
// 只有一个有参构造方法,Spring 自动注入参数
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
此时 Spring 的实例化策略:
java
// 源码简化
protected BeanWrapper autowireConstructor(
String beanName, RootBeanDefinition mbd,
Constructor<?>[] ctors, Object[] explicitArgs) {
// 使用 ConstructorResolver 解析并调用构造方法
return new ConstructorResolver(this).autowireConstructor(
beanName, mbd, ctors, explicitArgs);
}
ConstructorResolver 会根据构造方法的参数类型,从容器中查找匹配的 Bean 并注入。
3.3 工厂方法实例化
通过 @Bean 方法或 XML 中的 factory-method 定义 Bean 时,Spring 不会直接反射创建对象,而是调用工厂方法。
java
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
此时 Spring 调用 AppConfig 实例的 dataSource() 方法,获取返回值作为 Bean。源码走的是 instantiateUsingFactoryMethod(),逻辑是找到工厂 Bean 和对应的方法,通过反射调用该方法。
xml
<!-- XML 中的工厂方法 -->
<bean id="dataSource" class="com.example.DataSourceFactory" factory-method="create"/>
四、CGLIB 增强:为什么不能简单看成一个 new
对于普通的无参构造方法,Spring 确实是通过反射调用的。但 Spring 可能在实例化前对类进行 CGLIB 增强,生成代理类,再实例化代理类。
java
// 如果 Bean 需要 AOP 代理,Spring 会在 Bean 初始化完成后生成代理对象
// 但对 @Configuration 类本身,Spring 会通过 CGLIB 增强
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
// 这里的 userService() 调用会被 CGLIB 拦截
// 确保返回的是容器中的单例
return new UserService();
}
}
因此,@Configuration 类的实例化过程是:先通过 CGLIB 生成子类,再实例化子类。CGLIB 生成子类时默认调用父类的无参构造方法。
五、Bean 实例化后的处理链
对象实例化只是第一步,之后还有一系列处理:
1. 实例化(createBeanInstance) ← 反射调用构造方法
2. 属性填充(populateBean) ← 依赖注入(@Autowired、@Value)
3. 初始化前(BeanPostProcessor.postProcessBeforeInitialization)
4. 初始化(afterPropertiesSet、init-method、@PostConstruct)
5. 初始化后(BeanPostProcessor.postProcessAfterInitialization)← AOP 代理在这里生成
6. 注册到单例池
这个流程说明:new 对象只是起点,Spring 在 new 出来的对象上完成依赖注入、增强、回调等一系列操作,最终放入容器。
六、循环依赖与三级缓存
构造器注入与字段注入在循环依赖时的表现不同,这与实例化时机有关。
| 注入方式 | 实例化时机 | 循环依赖支持 |
|---|---|---|
| 无参构造 + 字段注入 | 先实例化,再注入属性 | ✅ 支持(通过三级缓存提前暴露早期引用) |
| 构造器注入 | 实例化时需要依赖,依赖需要先实例化 | ❌ 不支持(循环依赖时抛异常) |
Spring 解决循环依赖时,会提前暴露 ObjectFactory 到 singletonFactories(三级缓存),让依赖方先拿到早期引用。这进一步说明"实例化"和"属性注入"是两个分离的阶段。
七、几种实例化方式的对比
| 方式 | 触发条件 | 底层实现 | 实例化时机 |
|---|---|---|---|
| 无参构造 + 反射 | 默认方式 | SimpleInstantiationStrategy + Constructor.newInstance() |
属性注入前 |
| 有参构造 + 注入 | 无无参构造,有唯一有参构造 | ConstructorResolver.autowireConstructor() |
实例化时注入依赖 |
| 工厂方法 | @Bean 或 factory-method |
instantiateUsingFactoryMethod() |
调用工厂方法 |
| Supplier | registerBean 传入 Supplier |
Supplier.get() |
调用 Supplier |
| CGLIB 代理 | @Configuration 类、需要代理的 Bean |
CGLIB 子类 + 父类无参构造 | 初始化后 |
八、常见误区
误区一:Spring 只能通过无参构造方法创建对象
错误。构造器注入、工厂方法、Supplier 都是实例化方式,无参构造只是默认情况。
误区二:Spring 创建对象后就立即注入依赖
错误。实例化和属性注入是两个阶段。构造器注入的依赖在实例化时提供,字段注入的依赖在实例化后填充。
误区三:反射调用构造方法就等于 new
不完全相同。Constructor.newInstance() 与 new 关键字的行为非常接近,但反射允许访问私有构造方法,且在 JIT 优化下性能差距已大幅缩小。
九、总结
Spring 创建对象的核心机制是:默认通过反射调用无参构造方法实例化 Bean,再通过属性填充完成依赖注入,最后经过一系列后置处理(AOP 代理、初始化回调)注册到容器。 当 Bean 定义方式不同(有参构造、工厂方法、Supplier)时,实例化策略会相应变化,但反射始终是底层手段之一。
理解这个机制,就能解释很多现象:为什么循环依赖通过字段注入能解决而构造器注入不行、为什么 @Configuration 类的 @Bean 方法调用能返回单例、为什么私有构造方法在 Spring 中也能被调用。