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 解决循环依赖时,会提前暴露 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 中也能被调用。

相关推荐
十碗饭吃不饱几秒前
Apache POI 踩坑:setFontFamily 二次设置字体不生效、样式残留问题深度解析
java·word·apache
我命由我123454 分钟前
6-2.Python 异常处理 - 异常捕获(捕获指定异常、常见异常类型、异常信息)
java·运维·开发语言·python·http·https·java-ee
Flynn Liang8 分钟前
保姆级|Spring AI 2.0 第一个 Demo:Spring Boot 4 接入大模型(含国内可用方案 + 完整 pom)
人工智能·spring·ollama·springai·deepseek·springai入门·java接入大模型
狂师17 分钟前
夯爆了!分享几个我在用的,基于业务场景接口串联测试Skill !
人工智能·后端·测试
北京地铁1号线20 分钟前
后端开发:缓存(Cache)简介
后端·缓存·cache
IT_陈寒20 分钟前
Vue项目实战经验分享:动态组件在Keep-Alive下的缓存陷阱
前端·人工智能·后端
zaizai100721 分钟前
STL深度剖析2
java·c++·算法
程序猿乐锅32 分钟前
【从0-1学习Python | 第九篇】继承
开发语言·后端·python·学习·fastapi
2601_9540056234 分钟前
11 种触发 × 18 种行为:ricon 交互编排系统全解
前端·后端·物联网·数学建模·信息可视化·交互
周厉36 分钟前
WebSocket 使用场景与实现方法:Java + Vue3 全栈实战
java·websocket·vue3·websocket双向通讯