Spring 底层如何创建对象:反射机制与实例化策略

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 解决循环依赖时,会提前暴露 ObjectFactorysingletonFactories(三级缓存),让依赖方先拿到早期引用。这进一步说明"实例化"和"属性注入"是两个分离的阶段。

七、几种实例化方式的对比

方式 触发条件 底层实现 实例化时机
无参构造 + 反射 默认方式 SimpleInstantiationStrategy + Constructor.newInstance() 属性注入前
有参构造 + 注入 无无参构造,有唯一有参构造 ConstructorResolver.autowireConstructor() 实例化时注入依赖
工厂方法 @Beanfactory-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 中也能被调用。

相关推荐
Lyra_Infra1 小时前
从 MySQL 到达梦:一次信创隔离环境里的数据库迁移踩坑实录
数据库·后端·mysql
程序员天天困1 小时前
Kafka 接入 AI 的三条路线:MCP 提案、会话记忆与实时上下文
大数据·后端·kafka
何以解忧,唯有..1 小时前
Redis 过期事件监听:原理与实战
java
明月_清风1 小时前
用自然语言控制 Blender:开源项目 Blender MCP 深度介绍
人工智能·后端
风中的小熊生气2 小时前
Spring Boot 面试知识(二):分层、IoC、异常、配置与 Redis
java·springboot
泡茶喝茶写代码2 小时前
量化数据开发实战系列(第 25 篇):基金基础接口实战:基金列表、ETF-LOF 分类、基金概况、净值数据
java·数据库·人工智能·python·mysql
shehuiyuelaiyuehao2 小时前
算法46,分治快排,排序数组
java·算法·排序算法·排序
JaguarJack2 小时前
Whim 是什么?一门把 PHP 被否决的设想真跑起来的实验语言
后端·php·服务端
野生技术架构师2 小时前
1200 道 JAVA 面试题(各大企业常见面试题及答案)
java·开发语言