Spring Bean创建流程
使用Spring框架时,我们最直观的感受就是“不用自己new对象”——只需一个注解,Spring就会自动帮我们创建好所需的对象,然后通过getBean()就能直接获取使用。但你有没有好奇过:Spring到底是怎么找到要创建的类?又是怎么一步步把对象造出来的?尤其是当一个类有多个构造方法时,Spring会选哪一个?今天就来拆解Spring Bean的创建全过程,把这些底层逻辑讲明白。
一、先搞懂:Spring怎么“找到”要创建的类?
在开始创建对象之前,Spring首先要知道:我要创建哪些类的对象?这就涉及到Spring的扫描机制,我们从最常见的代码示例入手:
// 初始化Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取Bean对象
UserService userService = (UserService) context.getBean("userService");
userService.test();当我们执行第一行代码,初始化AnnotationConfigApplicationContext(Spring容器)时,Spring就已经开始“工作”了,核心就是找到所有需要创建Bean的类,具体分为3步:
1. 解析配置类,确定扫描路径
Spring会先解析我们传入的配置类(这里是AppConfig.class),不管配置类是用注解(@Configuration)还是XML配置,核心目的都是拿到包扫描路径——比如我们在AppConfig上加上@ComponentScan("com.vvhz.service"),Spring就知道要去这个包下找需要创建的类。
2. 扫描路径,筛选目标类
Spring会遍历扫描路径下的所有Java类,逐个判断:这个类是否需要被Spring管理?判断的标准很简单——只要类上有这些注解:@Component、@Service、@Controller、@Repository,就会被Spring“记下来”。
这里可以简单理解为:Spring内部有一个“注册表”(源码中叫BeanDefinitionMap,本质是一个Map),会把筛选出来的类存进去,key是后续要用到的beanName,value就是这个类本身(Class对象)。
3. 生成beanName,存入注册表(BeanDefinitionMap)
筛选出需要管理的类后,Spring会为每个类生成唯一的beanName(Bean的唯一标识),再将其与类的相关信息一起存入“注册表”——也就是Spring源码中的BeanDefinitionMap(本质是ConcurrentHashMap,保证线程安全)。
这里要明确:存入BeanDefinitionMap的并非Class对象本身,而是封装了类信息的BeanDefinition对象(包含类的全路径、构造方法、属性、作用域等),key是生成的beanName,value是对应的BeanDefinition。
这也就解释了我们调用getBean("userService")时,Spring是如何快速找到UserService类:Spring会先根据传入的beanName,去BeanDefinitionMap中查询对应的BeanDefinition,从BeanDefinition中获取到目标类(UserService)的信息,进而基于这些信息去创建Bean对象。
二、核心环节:Spring Bean的完整创建生命周期
找到目标类之后,Spring就会开始正式创建Bean对象,这个过程就像“流水线”,从实例化到最终可用,一共分为6个关键步骤,每一步都有明确的作用,我们逐个拆解:
步骤1:实例化——通过构造方法创建对象(核心:推断构造方法)
创建对象的第一步,和我们自己写Java代码一样:通过类的构造方法new出一个对象。但问题来了:如果一个类有多个构造方法(无参、有参),Spring会选哪一个?这就是构造方法推断,也是很多人容易困惑的点。
Spring的推断规则其实很简单,优先级从高到低:
如果有且只有一个构造方法(不管是无参还是有参),Spring直接用这个构造方法实例化;
如果有多个构造方法,且其中一个被@Autowired注解标记,Spring就用这个被注解的构造方法;
如果有多个构造方法,且没有任何一个被@Autowired注解,Spring会优先选择无参构造方法;如果没有无参构造方法,就会报错。
这里要注意:实例化只是创建了一个“空对象”(类似new UserService()),此时对象中的属性还都是默认值(null、0等),还不能直接使用。
步骤2:依赖注入——给对象的属性赋值
实例化得到空对象后,Spring会检查这个对象的属性:有没有被@Autowired、@Resource等注解标记的属性?如果有,Spring会从容器中找到对应的Bean,给这些属性赋值。
比如UserService中有一个UserDao属性,并且加了@Autowired注解:
@Service
public class UserService {
@Autowired
private UserDao userDao;
// 构造方法、test方法...
}Spring会在这一步,找到UserDao对应的Bean,自动给userDao赋值,不用我们手动new UserDao(),这就是Spring依赖注入的核心作用——解除类之间的依赖关系。
步骤3:Aware回调——让对象“知道”自己的环境
依赖注入完成后,Spring会判断:当前对象是否实现了Aware系列接口?比如BeanNameAware、BeanFactoryAware、BeanClassLoaderAware等。
这些接口的作用很简单:让Bean能获取到Spring容器的相关信息。比如实现了BeanNameAware接口,Spring就会调用该接口的setBeanName()方法,把当前Bean的beanName传入;实现了BeanFactoryAware,就能获取到Spring容器本身(BeanFactory)。
注意:这一步是Spring主动回调,我们只需要让Bean实现对应的接口,重写方法即可,不用手动调用。
步骤4:初始化前——执行@PostConstruct注解的方法
Aware回调之后,Spring会检查当前对象的方法:有没有被@PostConstruct注解标记的方法?如果有,Spring会立即调用这个方法。
这个方法是我们自己定义的,用来做一些初始化前的准备工作,比如加载资源、初始化参数等,它的执行时机是“依赖注入完成后,正式初始化前”。
@PostConstruct
public void initBefore() {
// 初始化前的操作,比如加载配置文件
System.out.println("初始化前:加载资源");
}步骤5:初始化——执行InitializingBean接口的方法
初始化前的方法执行完后,Spring会判断:当前对象是否实现了InitializingBean接口?如果实现了,就会调用该接口的afterPropertiesSet()方法。
这个方法和@PostConstruct的作用类似,也是用来做初始化操作,但它是Spring提供的接口方法,优先级比@PostConstruct低(先执行@PostConstruct,再执行afterPropertiesSet())。
步骤6:初始化后——AOP动态代理(可选)
这是Bean创建的最后一步,也是最关键的一步之一:Spring会判断当前Bean是否需要进行AOP(面向切面编程)。
什么情况下需要AOP?比如Bean上有@Transactional(事务)、@Aspect(切面)等注解,或者配置了AOP相关的规则,Spring就会对这个Bean进行动态代理,生成一个代理对象。
这里要重点区分:
如果不需要AOP:最终的Bean就是步骤1中通过构造方法实例化的对象(比如UserService本身);
如果需要AOP:最终的Bean不是原对象,而是Spring生成的代理对象(比如UserService的代理类实例),后续我们通过getBean()拿到的,也是这个代理对象。
到这里,一个完整的Spring Bean就创建完成了。
三、补充:单例Bean和原型Bean的区别
Bean创建完成后,Spring会根据Bean的作用域(scope),做不同的后续处理,最常见的就是单例(singleton)和原型(prototype):
1. 单例Bean(默认)
如果Bean是单例(默认作用域),Spring会把创建好的Bean对象存入一个“单例池”(源码中是一个Map,key是beanName,value是Bean对象)。
后续再调用getBean()获取这个Bean时,Spring不会再重新创建对象,而是直接从单例池中取出已有的对象,这也是单例Bean的核心特点——全局只有一个实例。
2. 原型Bean
如果Bean是原型作用域(通过@Scope("prototype")指定),Spring创建完Bean后,不会存入单例池,后续每次调用getBean(),都会重新执行上述完整的创建流程,生成一个新的Bean对象。
简单说:单例Bean“一次创建,多次复用”,原型Bean“每次获取,都是新的”。
四、总结:一张图看懂Bean创建全流程
最后用一张流程图,梳理一下整个Bean的创建过程,方便大家记忆:
Spring容器初始化 → 解析配置类 → 扫描路径筛选目标类 → 生成beanName存入注册表 → 推断构造方法实例化对象 → 依赖注入赋值 → Aware回调 → @PostConstruct(初始化前) → InitializingBean(初始化) → AOP动态代理(可选) → 存入单例池(单例Bean)/ 直接返回(原型Bean)
其实Spring创建Bean的过程,本质就是“从找到类,到造好对象,再到让对象可用”的过程,其中构造方法推断、依赖注入、AOP是最核心的三个环节。理解了这个过程,你就能更清晰地明白Spring的“自动装配”到底是怎么回事,后续看Spring源码也会更轻松。