Bean 的作用域
Spring Bean 的作用域定义了 Bean 在哪些上下文中可用。默认情况下是单例(Singleton),但在 Web 环境或特定场景下,原型(Prototype)、请求(Request)等作用域更为关键。
/**
* 实体类
*/
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
@ApplicationScope
public Dog applicationDog() {
return new Dog();
}
}
/**
* 启动类
*/
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
/**
* 视图层
*/
@RequestMapping("/test")
@RestController
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 和 Session 作用域,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:容器关闭时触发。
示例代码
/**
* 实体类
*/
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;
// 1. 实例化:执行构造方法
public BeanLifeComponent() {
log.info("1.实例化:执行构造方法");
}
// 2. 属性赋值:执行 setter 方法
@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");
}
}
/**
* 测试类
*/
@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();
}
}
运行日志显示顺序往往是:初始化回调 → 前置处理 → 后置处理。这看起来与理论流程相矛盾,其实是因为 BeanPostProcessor 本身也是 Bean。
当 Spring 初始化一个 BeanPostProcessor 实现类时,这个过程是递归的。Spring 需要先让这个 BeanPostProcessor 对象本身完成初始化(调用 invokeInitMethods),然后才能将它加入到列表中供后续其他 Bean 使用。但对于这个 BeanPostProcessor 自己来说,它自己的 afterPropertiesSet 会在初始化回调阶段执行,而它自己的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法不会在它自己的创建过程中被调用。因此,日志显示的其实是该 Bean 作为普通 Bean 被其他已初始化的 Processor 处理的过程。
根据源码分析,正确的生命周期流程应该是:前置处理 → 初始化回调 → 后置处理。
源码解析
核心类 AbstractAutowireCapableBeanFactory 负责 Bean 的创建、依赖注入及初始化管理。
- createBean 方法:调用了
doCreateBean方法。 - doCreateBean 方法:依次调用了
createBeanInstance(实例化)、populateBean(属性赋值)和initializeBean(初始化)。 - initializeBean 方法:依次调用了
invokeAwareMethods(通知方法)、applyBeanPostProcessorsBeforeInitialization(前置处理)、invokeInitMethods(初始化回调)和applyBeanPostProcessorsAfterInitialization(后置处理)。
SpringBoot 自动装配
自动装配的作用是注册 Bean 到 Spring 容器,不需要手动配置,通过'约定大于配置'的方式减少复杂性。简单来说,就是将依赖 Jar 包中的配置类以及 Bean 加载到 IoC 容器的过程。
加载 Bean 的几种方式
在 pom.xml 引入第三方依赖后,Spring Boot 项目启动时能识别这些依赖并自动加载。如果自定义的配置类不在扫描路径下,可以通过以下方式解决:
1. @ComponentScan
告诉 Spring 容器去哪里扫描标记了 @Component、@Service 等注解的类。
@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", "com.example.springprincicle.component.DemoConfig"};
}
}
3. 自定义注解
为了降低耦合,通常由依赖开发者提供一个注解,内部封装 @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:默认扫描启动类所在目录及其子目录。排除不需要扫描的配置类,防止重复注册。
- @SpringBootConfiguration:标记该类为 Spring 配置类,并提供索引支持加速启动。
- @EnableAutoConfiguration:这是自动装配的核心。
AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,负责动态加载自动配置类。在 Spring Boot 3.x 中,遵循'约定优于配置',第三方依赖库需要将其自动配置类定义在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。
getCandidateConfigurations 方法会扫描类路径下的 .imports 文件,读取所有找到的自动配置类名。同时,为了保持向后兼容,Spring Framework 仍然保留了 spring.factories 的支持,通过 SpringFactoriesLoader 加载相关的监听器事件。
这样,Spring Boot 启动时就能自动发现并加载这些配置,无需开发者手动干预。

