跳到主要内容
Spring Bean 作用域、生命周期与自动装配源码解析 | 极客日志
Java java
Spring Bean 作用域、生命周期与自动装配源码解析 综述由AI生成 Spring Bean 的作用域定义了 Bean 在哪些上下文中可用,包括 Singleton、Prototype、Request 等模式。Bean 生命周期涵盖实例化、属性赋值、初始化、使用和销毁五个阶段,其中初始化阶段涉及 Aware 接口、BeanPostProcessor 前后置处理及初始化回调。自动装配机制通过 @SpringBootApplication 组合注解,利用 ComponentScan、Import 及 EnableAutoConfiguration 实现依赖的自动加载,核心在于读取 META-INF 下的配置文件动态注册 Bean。
林间仙子 发布于 2026/3/15 更新于 2026/4/24 2 浏览Spring Bean 作用域、生命周期与自动装配源码解析
1. Bean 的作用域
Spring Bean 的作用域决定了 Bean 在哪些上下文中可用。默认情况下,Spring 容器中的 Bean 是单例的(Singleton),但在 Web 应用中,我们往往需要更灵活的生命周期管理。
常见作用域示例
我们可以通过 @Scope 注解或特定的 Scope 注解来定义 Bean 的作用范围。下面是一个包含多种作用域的示例配置:
public class Dog {}
@Configuration
public class DogConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog () {
return new Dog ();
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog () {
return new Dog ();
}
@Bean
@RequestScope
public Dog requestDog () {
return new Dog ();
}
@Bean
@SessionScope
public Dog sessionDog () {
();
}
Dog {
();
}
}
return
new
Dog
@Bean
@ApplicationScope
public
applicationDog
()
return
new
Dog
在 Controller 中注入这些 Bean 时,行为会有显著差异。例如,当使用 @Autowired 注入 Prototype 作用域的 Bean 到 Singleton 的 Controller 中时,注入操作仅在容器初始化阶段执行一次。这意味着后续通过字段引用访问的是最初注入的那个实例,不会因后续请求自动重新注入新实例。
对于 Request 作用域的 Bean,Spring 实际上注入的是一个代理对象而非真实实例。这个代理对象在应用启动时就被注入到依赖它的单例 Bean 中,但真实实例的创建被延迟到 HTTP 请求发生时。代理对象内部持有对当前 HTTP 请求上下文的引用,当调用代理对象的方法时,它会从当前请求的上下文中查找或创建新的真实实例。这种机制确保了每个请求线程都能获得独立的实例。
Singleton(单例) :默认作用域,每个 Spring 容器中仅存在一个 Bean 实例。
Prototype(原型) :每次请求 Bean 时都会创建一个新的实例。
Request(请求) :每个 HTTP 请求创建一个新的 Bean 实例,仅在 Web 应用中有效。
Session(会话) :每个用户会话创建一个 Bean 实例,仅在 Web 应用中有效。
Application(应用) :整个 Web 应用共享一个 Bean 实例。
2. Bean 的生命周期 Bean 的生命周期指的是一个对象从创建到销毁的整个过程。理解这一过程对于调试和自定义 Bean 行为至关重要。
生命周期阶段
实例化 :容器通过反射调用 Bean 的构造器创建对象实例。
属性赋值 :容器注入依赖的属性值(例如通过 @Autowired)。
初始化 :
通知方法调用 :通过特定接口(如 BeanNameAware)注入框架相关依赖或上下文信息。
前置处理 :进行准备工作,例如参数校验、资源加载或权限检查。
初始化回调 :在对象初始化阶段触发的自定义逻辑(如实现 InitializingBean 接口的 afterPropertiesSet 方法)。
后置处理 :在流程结束后执行清理或结果处理(如 AOP 中的 @After 通知)。
使用 Bean :Bean 进入就绪状态,可被应用程序调用。
销毁 Bean :容器关闭时触发销毁。
代码验证 为了直观地观察这些阶段,我们可以编写一个简单的测试类来实现相关的 Aware 接口和回调方法:
public class Cat {}
@Configuration
public class CatConfig {
@Bean
public Cat cat () {
return new Cat ();
}
}
@Component
@Slf4j
public class BeanLifeComponent implements BeanNameAware , BeanPostProcessor, InitializingBean {
private Cat cat;
public BeanLifeComponent () {
log.info("1.实例化:执行构造方法" );
}
@Autowired
public void setCat (Cat cat) {
log.info("2.属性赋值:执行 setter 方法" );
this .cat = cat;
}
@Override
public void setBeanName (String name) {
log.info("3.1 通知方法调用,bean name is {}" , name);
}
@Override
public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException {
log.info("3.3 前置处理,bean:{},beanName:{}" , bean, beanName);
return BeanPostProcessor.super .postProcessBeforeInitialization(bean, beanName);
}
@Override
public void afterPropertiesSet () {
log.info("3.2 初始化回调" );
}
@Override
public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException {
log.info("3.4 后置处理,bean:{},beanName:{}" , bean, beanName);
return BeanPostProcessor.super .postProcessAfterInitialization(bean, beanName);
}
public void use () {
log.info("4.使用 Bean" );
}
@PreDestroy
public void preDestroy () {
log.info("5.销毁 Bean" );
}
}
运行测试后,日志显示的顺序通常是:初始化回调 → 前置处理 → 后置处理。这看起来似乎与上述介绍的生命周期流程相矛盾,具体原因我们需要结合源码来分析。
源码解析 核心逻辑主要在 AbstractAutowireCapableBeanFactory 类中,它负责 Bean 的创建、依赖注入以及初始化等生命周期管理。
创建 Bean :搜索 createBean 方法,最终会调用 doCreateBean 方法。
实例化与填充 :doCreateBean 中依次调用了 createBeanInstance(实例化)、populateBean(属性赋值)和 initializeBean(初始化)方法。
初始化细节 :initializeBean 方法中依次调用了 invokeAwareMethods、applyBeanPostProcessorsBeforeInitialization、invokeInitMethods 和 applyBeanPostProcessorsAfterInitialization 方法。
为什么日志顺序与预期不同? 根据源码来看,正确的生命周期流程应该是:前置处理 → 初始化回调 → 后置处理。那么为什么实际日志显示的是初始化回调在前呢?
关键在于:BeanPostProcessor 本身也是 Bean 。当 Spring 初始化一个 BeanPostProcessor 实现类(如上面的 BeanLifeComponent)时,这个过程是递归的。
创建非 BeanPostProcessor Bean :按照源码顺序正常执行:BeforeInitialization → InitMethods → AfterInitialization。
创建 BeanPostProcessor Bean 时 :Spring 需要先让这个 BeanPostProcessor 对象本身完成初始化(调用 invokeInitMethods),然后才能将它加入到 BeanPostProcessor 列表中,供后续其他 Bean 使用。
自身处理 :但对于这个 BeanPostProcessor Bean 自己来说,它自己的 afterPropertiesSet 方法会在 invokeInitMethods 中执行(初始化回调阶段),但它自己的 postProcessBeforeInitialization / postProcessAfterInitialization 方法不会在它自己的创建过程中被调用!
因此,日志中显示的顺序实际上是 BeanLifeComponent 作为普通 Bean 被初始化时的回调顺序,而它作为 BPP 生效是在后续其他 Bean 初始化时才发生的。
3. SpringBoot 自动装配 自动装配的作用是自动注册 Bean 到 Spring 容器,不需要手动配置。通过'约定大于配置'的方式减少手动配置的复杂性。换言之,Spring Boot 的自动配置就是将依赖 Jar 包中的配置类以及 Bean 加载到 IoC 容器的过程。
3.1 SpringBoot 加载 Bean 在 pom.xml 文件中引入第三方依赖,实际上就是将第三方代码引入到 Spring Boot 项目中。Spring Boot 项目在启动时能识别这些依赖并自动将它们的配置类以及 Bean 加载到 IoC 容器。
如果编写的配置类不在启动类的同一目录或其子目录下,Spring 默认可能无法扫描到。此时可以通过以下几种方式解决:
@ComponentScan 告诉 Spring 容器去哪里扫描那些被 @Component、@Service、@Repository、@Controller 等注解标记的类,并将它们自动注册为 Bean。
@SpringBootApplication
@ComponentScan("com.example.springprincicle.component")
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
@Import 用于将一个或多个配置类、组件类或其他类导入到当前的 Spring 应用上下文中。
@SpringBootApplication
@Import(TestConfig.class)
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String []{"com.example.springprincicle.component.TestConfig" , "com.example.springprincicle.component.DemoConfig" };
}
}
自定义注解 在使用 @Import 注解导入 Bean 时,需要程序员熟悉第三方依赖的所有配置类,这对于开发程序十分不友好。所以,应该由依赖的开发者来做这件事。比较常见的方案就是第三方依赖给我们提供一个注解,该注解内部封装 @Import 注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableTestConfig {}
@SpringBootApplication
@EnableTestConfig
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
3.2 @SpringBootApplication 源码解析 @SpringBootApplication 是一个组合注解,包含以下三个核心注解的功能:
@ComponentScan 在 @SpringBootApplication 注解中,如果没有指定扫描路径,那么默认扫描路径为 @SpringBootApplication 标注的类的类路径。这确保了启动类所在包及其子包下的组件能被自动发现。
@SpringBootConfiguration 标记该类为 Spring 的配置类。同时利用 @Indexed 为 Spring 的组件扫描提供索引支持,加速应用启动时的类加载过程。
@EnableAutoConfiguration 这是自动装配的核心。AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,负责在 Spring 应用启动时动态加载自动配置类。
其核心逻辑在于 getCandidateConfigurations 方法。在 Spring Boot 3.x 版本中,该方法不仅加载 Spring Boot 默认的自动配置类,还会加载所有第三方库提供的自动配置类。遵循 Spring Boot 的'约定优于配置'理念,第三方依赖库需要将其自动配置类定义在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中,这样 Spring Boot 启动时就能自动发现并加载这些配置。
此外,为了向后兼容,Spring Framework 仍然保留了 spring.factories 的支持,体现在 fireAutoConfigurationImportEvents 方法中。即使在新机制下,旧的文件格式依然能被识别。
protected List<String> getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this .autoConfigurationAnnotation, getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.state(!CollectionUtils.isEmpty(configurations), "No auto configuration classes found..." );
return configurations;
}
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online