跳到主要内容Spring Bean 作用域、生命周期与自动装配源码解析 | 极客日志Javajava
Spring Bean 作用域、生命周期与自动装配源码解析
综述由AI生成Spring Bean 作用域涵盖单例、原型及 Web 相关范围,不同作用域下实例创建时机各异。生命周期涉及实例化、属性赋值、初始化、使用和销毁五个阶段,其中 BeanPostProcessor 的递归初始化特性可能导致日志顺序看似异常。自动装配通过约定大于配置原则,利用@ComponentScan、@Import 及@EnableAutoConfiguration 等机制,结合 spring.factories 或 AutoConfiguration.imports 文件,实现依赖 Jar 包中配置类和 Bean 的自动加载,简化了手动配置工作。
星河入梦2 浏览 Spring Boot 版本
本文基于 3.5.8 版本进行讲解。
Bean 的作用域
Bean 的作用域决定了它在哪些上下文中可用。Spring 支持多种作用域,包括单例、原型以及 Web 相关的请求、会话和应用范围。
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() {
return new Dog();
}
Dog {
();
}
}
@Bean
@ApplicationScope
public
applicationDog
()
return
new
Dog
启动类使用 @SpringBootApplication 即可,这里不再赘述。重点看 Controller 中的注入行为:
@RestController
@RequestMapping("/test")
public class TestController {
private final Dog single;
private final Dog prototype;
private final Dog request;
private final Dog session;
private final Dog application;
private final ApplicationContext context;
public TestController(@Qualifier("singleDog") Dog single,
@Qualifier("prototypeDog") Dog prototype,
@Qualifier("requestDog") Dog request,
@Qualifier("sessionDog") Dog session,
@Qualifier("applicationDog") Dog application,
ApplicationContext context) {
this.single = single;
this.prototype = prototype;
this.request = request;
this.session = session;
this.application = application;
this.context = context;
}
@RequestMapping("/single")
public String single() {
Dog singleDog = (Dog) context.getBean("singleDog");
return "dog: " + this.single.toString() + "<br>" + "contextDog: " + singleDog;
}
@RequestMapping("/prototype")
public String prototype() {
Dog prototypeDog = (Dog) context.getBean("prototypeDog");
return "dog: " + this.prototype.toString() + "<br>" + "contextDog: " + prototypeDog;
}
@RequestMapping("/request")
public String request() {
Dog requestDog = (Dog) context.getBean("requestDog");
return "dog: " + this.request.toString() + "<br>" + "contextDog: " + requestDog;
}
@RequestMapping("/session")
public String session() {
Dog sessionDog = (Dog) context.getBean("sessionDog");
return "dog: " + this.session.toString() + "<br>" + "contextDog: " + sessionDog;
}
@RequestMapping("/application")
public String application() {
Dog applicationDog = (Dog) context.getBean("applicationDog");
return "dog: " + this.application.toString() + "<br>" + "contextDog: " + applicationDog;
}
}
- 直接通过
context.getBean() 获取会触发新实例创建(针对非单例)。
- 当使用
@Autowired 或 @Resource 注入 Prototype 作用域的 Bean 时,注入操作仅在初始化阶段执行一次。后续通过字段引用访问的是最初注入的实例,不会因后续请求自动重新注入新实例。
- 对于 Request 作用域,Spring 实际上注入的是一个代理对象而非真实实例。代理对象在应用启动时就被注入到依赖它的单例 Bean 中,但真实实例的创建被延迟到 HTTP 请求发生时。代理对象内部持有对当前 HTTP 请求上下文的引用,调用方法时会从上下文中查找或创建新的真实实例。这种延迟查找机制确保每个请求线程都能获得独立的实例。
- Singleton(单例):默认作用域,每个 Spring 容器中仅存在一个 Bean 实例。
- Prototype(原型):每次请求 Bean 时都会创建一个新的实例。
- Request(请求):每个 HTTP 请求创建一个新的 Bean 实例,仅在 Web 应用中有效。
- Session(会话):每个用户会话创建一个 Bean 实例,仅在 Web 应用中有效。
- Application(应用):整个 Web 应用共享一个 Bean 实例。
Bean 的生命周期
Bean 的生命周期指的是从创建到销毁的整个过程,主要包含五个阶段:
- 实例化:容器通过反射调用 Bean 的构造器创建对象实例。
- 属性赋值:容器注入依赖的属性值(例如
@Autowired)。
- 初始化:
- 通知方法调用:通过特定接口(如
BeanNameAware)注入框架相关依赖或上下文信息。
- 前置处理:进行准备工作,例如参数校验、资源加载或权限检查。
- 初始化回调:在对象初始化阶段触发的自定义逻辑(如实现
InitializingBean 接口的 afterPropertiesSet 方法)。
- 后置处理:在流程结束后执行清理或结果处理(如 AOP 中的
@After 通知)。
- 使用 Bean:Bean 进入就绪状态,可被应用程序调用。
- 销毁 Bean:容器关闭时触发销毁。
示例代码
@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");
}
}
运行测试类后,日志显示的顺序往往是:invokeInitMethods(初始化回调) → applyBeanPostProcessorsBeforeInitialization(前置处理) → applyBeanPostProcessorsAfterInitialization(后置处理)。这看起来似乎与上述介绍的生命周期流程相矛盾,具体原因在源码解析部分说明。
源码解析
核心类是 AbstractAutowireCapableBeanFactory,它主要负责 Bean 的创建、依赖注入以及初始化等生命周期管理。
- 搜索
createBean 方法:这是 Bean 创建的核心入口。
doCreateBean 方法:依次调用了 createBeanInstance、populateBean 和 initializeBean 方法。
initializeBean 方法:这是初始化的关键,依次调用了 invokeAwareMethods、applyBeanPostProcessorsBeforeInitialization、invokeInitMethods 和 applyBeanPostProcessorsAfterInitialization。
日志与源码'冲突'的原因分析
根据源码来看,正确的生命周期流程应该是:前置处理 → 初始化回调 → 后置处理。那为什么日志显示不一致呢?
关键在于:BeanPostProcessor 本身也是 Bean。当 Spring 初始化一个 BeanPostProcessor 实现类(比如上面的 BeanLifeComponent)时,这个过程是递归的。
- 创建普通 Bean:按照源码顺序正常执行:BeforeInitialization → InitMethods → AfterInitialization。
- 创建 BeanPostProcessor Bean 时:
- Spring 需要先让这个
BeanPostProcessor 对象本身完成初始化(调用 invokeInitMethods)。
- 然后才能将它加入到
BeanPostProcessor 列表中,供后续其他 Bean 使用。
- 但对于这个
BeanPostProcessor Bean 自己来说,它自己的 afterPropertiesSet 方法会在 invokeInitMethods 中执行(初始化回调阶段),但它自己的 postProcessBeforeInitialization / postProcessAfterInitialization 方法不会在它自己的创建过程中被调用!
- 3.2 初始化回调 - 这个
BeanPostProcessor Bean 自己的 afterPropertiesSet()。
- 3.3 前置处理 -
BeanLifeComponent 类对这个 BeanPostProcessor Bean 的处理(注意这里是它作为处理器去处理自己,或者后续处理其他 Bean时的体现)。
- 3.4 后置处理 -
BeanLifeComponent 类对这个 BeanPostProcessor Bean 的处理。
Spring Boot 自动装配
自动装配的作用是注册 Bean 到 Spring 容器,不需要手动配置,通过约定大于配置的方式减少手动配置的复杂性。换言之,Spring Boot 的自动配置就是将依赖 Jar 包中的配置类以及Bean加载到 Ioc 容器的过程。
SpringBoot 加载 Bean
在 pom.xml 文件中引入第三方依赖,实际上就是将第三方代码引入到 Spring Boot 项目中。Spring Boot 项目在启动时能识别这些依赖并自动将它们的配置类以及 Bean 加载到 Ioc 容器的过程。
如果 Bean 不在默认扫描路径下,运行项目可能会报错。解决方式主要有以下几种:
1. @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);
}
}
2. @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"};
}
}
3. 自定义注解
在使用 @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);
}
}
@SpringBootApplication 源码解析
- @ComponentScan:默认扫描路径为
@SpringBootApplication 标注的类的类路径。排除不需要扫描的配置类,防止自动配置类被重复扫描或注册。
- @SpringBootConfiguration:标记该类为 Spring 的配置类,并提供索引支持,加速应用启动时的类加载过程。
- @EnableAutoConfiguration:负责在 Spring 应用启动时动态加载自动配置类。
核心类是 AutoConfigurationImportSelector,它实现了 DeferredImportSelector 接口。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其中 getCandidateConfigurations 方法不仅加载 Spring Boot 默认的自动配置类,还会加载所有第三方库提供的自动配置类。它扫描类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。
遵循 Spring Boot 的'约定优于配置'理念,第三方依赖库需要将其自动配置类定义在该文件中,这样 Spring Boot 启动时就能自动发现并加载这些配置。
此外,fireAutoConfigurationImportEvents 体现了 Spring Framework/Spring Boot 的向后兼容性设计。即使在 Spring Boot 3.x 中引入了新的 .imports 机制,Spring Framework 仍然保留了 spring.factories 的支持。
相关免费在线工具
- 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