跳到主要内容
Spring Bean 作用域、生命周期与自动装配深度解析 | 极客日志
Java java
Spring Bean 作用域、生命周期与自动装配深度解析 本文深入探讨 Spring Bean 的核心机制,涵盖五种作用域的区别及原型模式下的注入陷阱,详解从实例化到销毁的五阶段生命周期及其源码执行顺序,剖析 Spring Boot 通过约定优于配置实现自动装配的原理,包括组件扫描、Import 导入及 EnableAutoConfiguration 背后的加载流程。
BackendPro 发布于 2026/3/28 0 浏览
Spring Boot 版本:3.x
Bean 的作用域
Bean 的作用域决定了它在容器中的存在范围。Spring 默认是单例(Singleton),但在 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 ();
}
@Bean
Dog {
();
}
}
{
{
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
{
Dog single;
Dog prototype;
Dog request;
Dog session;
Dog application;
ApplicationContext context;
{
.single = single;
.prototype = prototype;
.request = request;
.session = session;
.application = application;
.context = context;
}
String {
(Dog) context.getBean( );
+ .single.toString() + + + singleDog;
}
String {
(Dog) context.getBean( );
+ .prototype.toString() + + + prototypeDog;
}
String {
(Dog) context.getBean( );
+ .request.toString() + + + requestDog;
}
String {
(Dog) context.getBean( );
+ .session.toString() + + + sessionDog;
}
String {
(Dog) context.getBean( );
+ .application.toString() + + + applicationDog;
}
}
@ApplicationScope
public
applicationDog
()
return
new
Dog
@SpringBootApplication
public
class
SpringPrincipleApplication
public
static
void
main
(String[] args)
@RequestMapping("/test")
@RestController
public
class
TestController
private
final
private
final
private
final
private
final
private
final
private
final
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
this
this
this
this
this
@RequestMapping("/single")
public
single
()
Dog
singleDog
=
"singleDog"
return
"dog: "
this
"<br>"
"contextDog: "
@RequestMapping("/prototype")
public
prototype
()
Dog
prototypeDog
=
"prototypeDog"
return
"dog: "
this
"<br>"
"contextDog: "
@RequestMapping("/request")
public
request
()
Dog
requestDog
=
"requestDog"
return
"dog: "
this
"<br>"
"contextDog: "
@RequestMapping("/session")
public
session
()
Dog
sessionDog
=
"sessionDog"
return
"dog: "
this
"<br>"
"contextDog: "
@RequestMapping("/application")
public
application
()
Dog
applicationDog
=
"applicationDog"
return
"dog: "
this
"<br>"
"contextDog: "
这里有个坑需要注意:直接通过 context.getBean() 获取会触发新实例创建(针对非单例)。但当你使用 @Autowired 或 @Resource 注入原型(Prototype)作用域的 Bean 时,注入操作仅在容器初始化阶段执行一次,后续访问的都是最初注入的实例。
对于 Request 作用域,Spring 注入了一个代理对象。这个代理在启动时就被注入到单例 Bean 中,但真实实例的创建被延迟到了 HTTP 请求发生时。代理内部持有当前请求上下文,调用方法时会查找或创建新的实例,确保每个请求线程都能获得独立副本。
Singleton(单例) :默认作用域,整个容器中仅存在一个实例。
Prototype(原型) :每次请求 Bean 时都会创建新实例。
Request(请求) :每个 HTTP 请求创建一个新实例。
Session(会话) :每个用户会话创建一个实例。
Application(应用) :整个 Web 应用共享一个实例。
Bean 的生命周期 Bean 的生命周期涵盖了从创建到销毁的全过程,主要分为五个阶段:
实例化 :容器通过反射调用构造器创建对象。
属性赋值 :依赖注入(如 @Autowired)在此阶段完成。
初始化 :
通知方法调用 :实现 BeanNameAware 等接口注入框架信息。
前置处理 :BeanPostProcessor.postProcessBeforeInitialization,做参数校验或资源加载。
初始化回调 :实现 InitializingBean 的 afterPropertiesSet 或自定义 init-method。
后置处理 :BeanPostProcessor.postProcessAfterInitialization,常用于 AOP 代理生成。
使用 Bean :进入就绪状态,可被调用。
销毁 Bean :容器关闭时触发 @PreDestroy 等方法。
示例
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" );
}
}
@SpringBootTest
class SpringPrincipleApplicationTests {
private final ApplicationContext context;
@Autowired
public SpringPrincipleApplicationTests (ApplicationContext context) {
this .context = context;
}
@Test
public void test () {
BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
beanLifeComponent.use();
}
}
运行日志可能会让你困惑:为什么显示的是 invokeInitMethods 在前,而源码逻辑似乎是先处理?
关键在于:BeanPostProcessor 本身也是 Bean 。当 Spring 初始化一个实现了 BeanPostProcessor 的类(如上面的 BeanLifeComponent)时,这个过程是递归的。
创建普通 Bean 时,按顺序执行:Before → Init → After。
创建 BeanPostProcessor Bean 时,Spring 需要先让它自己完成初始化(包括调用 invokeInitMethods),才能将其加入列表供其他 Bean 使用。
但对于这个 Processor 自身而言,它的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法不会在它自己的创建过程中被调用!
所以日志显示的其实是该 BeanPostProcessor 自身的初始化过程:先执行了它自己的 afterPropertiesSet(初始化回调),然后才轮到它去处理其他 Bean 的前置/后置逻辑。这解释了看似矛盾的执行顺序。
SpringBoot 自动装配 自动装配的核心是'约定大于配置'。Spring Boot 启动时会自动扫描依赖包中的配置类和 Bean,无需手动注册。
加载原理 引入第三方依赖后,Spring Boot 能识别并加载其中的配置。如果组件不在默认扫描路径下,就需要手动干预。
1. @ComponentScan 告诉容器去哪里扫描标记了 @Component 等注解的类。
@SpringBootApplication
@ComponentScan("com.example.springprincicle.component")
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
2. @Import @SpringBootApplication
@Import(TestConfig.class)
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
或者通过 ImportSelector 接口动态选择:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String []{"com.example.springprincicle.component.TestConfig" };
}
}
为了封装复杂度,通常由依赖方提供一个自定义注解,内部封装 @Import。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
@interface EnableTestConfig {}
@SpringBootApplication
@EnableTestConfig
public class SpringPrincipleApplication {
public static void main (String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
@SpringBootApplication 源码解析
@ComponentScan :默认扫描启动类所在包及其子包。
@SpringBootConfiguration :标记为配置类,提供索引支持加速启动。
@EnableAutoConfiguration :这是自动装配的入口。
@EnableAutoConfiguration 背后是 AutoConfigurationImportSelector 类,它实现了 DeferredImportSelector 接口,负责动态加载自动配置类。
在 getCandidateConfigurations 方法中,Spring Boot 会扫描类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(Spring Boot 3.x 新机制),读取所有自动配置类名。旧版本则依赖 META-INF/spring.factories。
这种设计保证了向后兼容性,同时让第三方库只需将配置类定义在指定文件中,即可在启动时被自动发现并加载到 IoC 容器。
相关免费在线工具 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