Spring Bean 作用域
Spring 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();
}
// 请求:每个 HTTP 请求一个新实例
@Bean
@RequestScope
public Dog requestDog() {
return new Dog();
}
// 会话:每个用户会话一个新实例
@Bean
@SessionScope
public Dog sessionDog() {
return new Dog();
}
// 应用:整个 Web 应用共享一个实例
@Bean
@ApplicationScope
public Dog applicationDog() {
return new Dog();
}
}
启动类如下:
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
在控制器中注入这些 Bean 时,行为会有所不同:
- 直接通过 context.getBean():会触发新实例的创建(针对非单例)。
- @Autowired/@Resource 注入:
- 对于
prototype作用域的 Bean,注入操作仅在容器初始化阶段执行一次。后续通过字段访问的是最初注入的实例,不会自动更新。 - 对于
request作用域的 Bean,Spring 实际上注入的是一个代理对象。这个代理对象在启动时被注入到单例 Bean 中,但真实实例的创建被延迟到了 HTTP 请求发生时。
- 对于
核心机制说明:
- 代理注入:当使用
@Autowired注入 Request 作用域的 Bean 时,Spring 注入的是代理对象而非真实实例。代理对象持有当前 HTTP 请求上下文的引用。 - 方法调用:当调用代理对象的方法时,它会从当前请求的上下文中查找或创建新的真实实例。
- 实例操作:虽然依赖注入发生在容器初始化阶段,但通过代理模式将实例获取延迟到实际方法调用时,确保每个请求线程都能获得独立的实例。

Bean 的生命周期
Bean 的生命周期涵盖了从创建到销毁的全过程,主要分为五个阶段:
- 实例化:容器通过反射调用构造器创建对象。
- 属性赋值:容器注入依赖的属性值(如
@Autowired)。 - 初始化:
- 通知方法调用:实现
BeanNameAware等接口注入框架信息。 - 前置处理:
BeanPostProcessor.postProcessBeforeInitialization,用于参数校验或资源加载。 - 初始化回调:实现
InitializingBean接口的afterPropertiesSet方法。 - 后置处理:
BeanPostProcessor.postProcessAfterInitialization,常用于 AOP 代理生成。
- 通知方法调用:实现
- 使用 Bean:进入就绪状态,可被应用程序调用。
- 销毁 Bean:容器关闭时触发
@PreDestroy等方法。
示例代码
@Component
@Slf4j
public class BeanLifeComponent implements BeanNameAware, BeanPostProcessor, InitializingBean {
private Cat cat;
// 1. 实例化
public BeanLifeComponent() {
log.info("1.实例化:执行构造方法");
}
// 2. 属性赋值
@Autowired
public void setCat(Cat cat) {
log.info("2.属性赋值:执行 setter 方法");
this.cat = cat;
}
// 3.1 通知方法调用
@Override
public void setBeanName(String name) {
log.info("3.1 通知方法调用,bean name is {}", name);
}
// 3.2 前置处理
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("3.3 前置处理,bean:{},beanName:{}", bean, beanName);
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 3.3 初始化回调
@Override
public void afterPropertiesSet() {
log.info("3.2 初始化回调");
}
// 3.4 后置处理
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("3.4 后置处理,bean:{},beanName:{}", bean, beanName);
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
// 4. 使用 Bean
public void use() {
log.info("4.使用 Bean");
}
// 5. 销毁 Bean
@PreDestroy
public void preDestroy() {
log.info("5.销毁 Bean");
}
}
运行测试后,日志顺序可能显示为:初始化回调 → 前置处理 → 后置处理。这看起来与理论流程矛盾,其实是因为 BeanPostProcessor 本身也是 Bean。当 Spring 初始化一个实现了 BeanPostProcessor 的 Bean 时,这个过程是递归的。该 Bean 自身的 afterPropertiesSet 会在其初始化阶段执行,但它自己的 postProcessBeforeInitialization 方法不会在它自己的创建过程中被调用,而是由其他已初始化的处理器来调用它。因此,日志显示的其实是该 Bean 作为普通 Bean 被其他处理器处理的过程。

SpringBoot 自动装配
自动装配的核心在于'约定大于配置',Spring Boot 会自动扫描并注册依赖包中的配置类和 Bean,无需手动编写大量 XML 或注解。
加载 Bean 的原理
在 pom.xml 引入第三方依赖后,Spring Boot 启动时会识别这些依赖并加载其配置。如果自定义的配置类不在默认扫描路径下,可以通过以下方式解决:
1. @ComponentScan
指定扫描路径,告诉容器去哪里寻找组件。
@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"};
}
}
3. 自定义注解封装
为了更友好地暴露功能,通常会将 @Import 封装在自定义注解中。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableTestConfig {}
@SpringBootApplication 源码解析
这是一个组合注解,包含三个核心部分:
- @ComponentScan:默认扫描启动类所在包及其子包。如果没有指定路径,则以此为根目录。
- @SpringBootConfiguration:标记该类为配置类,支持索引加速启动。
- @EnableAutoConfiguration:开启自动配置。
自动配置加载流程
@EnableAutoConfiguration 内部使用了 AutoConfigurationImportSelector 类。该类实现了 DeferredImportSelector 接口,负责动态加载自动配置类。
在 Spring Boot 3.x 版本中,加载逻辑主要遵循以下路径:
- 扫描类路径下的
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。 - 读取文件中定义的自动配置类名列表。
- 排除被禁用的配置类(通过
spring.autoconfigure.exclude配置)。
旧版本的 spring.factories 机制在 Spring Boot 3.x 中依然保留以兼容旧项目,但推荐使用 .imports 文件。这种设计体现了 Spring Boot 向后兼容的理念。
// AutoConfigurationImportSelector 关键逻辑示意
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
// ... 过滤和去重 ...
return configurations;
}
通过这种方式,第三方库只需在资源文件中声明配置类,Spring Boot 启动时便能自动发现并加载,极大简化了集成过程。


