文章目录
- [Ⅰ. Bean的作用域](#Ⅰ. Bean的作用域)
- [Ⅱ. Bean的生命周期](#Ⅱ. Bean的生命周期)
- [Ⅲ. SpringBoot自动配置](#Ⅲ. SpringBoot自动配置)
-
- [一、Spring 加载 Bean](#一、Spring 加载 Bean)
-
- [1. 问题描述](#1. 问题描述)
- [2. 原因分析](#2. 原因分析)
- [3. 解决方案](#3. 解决方案)
-
- [① `@ComponentScan`](#①
@ComponentScan) - [② `@Import`](#②
@Import) -
- [1. 导入类](#1. 导入类)
- [2. 导入 `ImportSelector` 接口实现类](#2. 导入
ImportSelector接口实现类) - **如何解决上述两种方式的问题❓**
- [① `@ComponentScan`](#①
- 二、自动装配原理
-
- [1. 源码阅读](#1. 源码阅读)
- [2. `@EnableAutoConfiguration` 详解](#2.
@EnableAutoConfiguration详解) -
- [① `@Import({AutoConfigurationImportSelector.class})`](#①
@Import({AutoConfigurationImportSelector.class})) - [② `@AutoConfigurationPackage`](#②
@AutoConfigurationPackage)
- [① `@Import({AutoConfigurationImportSelector.class})`](#①
- [3. 总结](#3. 总结)

Ⅰ. Bean的作用域
一、概念
在 Spring IoC 和 DI 部分学习了 Spring 是如何帮助我们管理对象的。
- 通过五大类注解
@Controller、@Service、@Repository、@Component、@Configuration和方法注解@Bean来声 Bean 对象。 - 通过
ApplicationContext或者BeanFactory来获取对象 - 通过
@Autowired、Setter方法或者构造方法等来为应用程序注入所依赖的Bean对象
默认情况下,Spring 容器中的 bean 都是单例的,这种行为模式就称之为 Bean 的作用域。
二、Bean的作用域
| 名称 | 生命周期 | 作用粒度 | 底层对象 | 所属环境 |
|---|---|---|---|---|
| singleton | Spring 容器存活期间 | 容器级全局共享 | Spring 容器 | 所有环境通用 |
| prototype | 每次获取新建实例 | 每次请求 Bean 时 | Spring 容器 | 所有环境通用 |
| request | 一次 HTTP 请求 | 单次请求内共享 | HttpServletRequest |
仅 Web 环境 |
| session | 一次用户会话 | 单个用户会话级别 | HttpSession |
仅 Web 环境 |
| application | 整个 Web 应用 | 全局共享 | ServletContext |
仅 Web 环境 |
| websocket | 一次 WebSocket 连接 | 单连接 | WebSocket 会话 | 仅 WebSocket |
定义几个不同作用域的 Bean,需要搭配不同的注解,如下所示:
java
@Component
public class DogBeanConfig {
@Bean // 默认是单例singleton
public Dog dog(){
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@RequestScope
public Dog requestDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@SessionScope
public Dog sessionDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@ApplicationScope
public Dog applicationDog() {
Dog dog = new Dog();
return dog;
}
}
测试不同作用域的Bean取到的对象是否一样:
java
@RestController
public class DogController {
@Autowired
private Dog singleDog;
@Autowired
private Dog prototypeDog;
@Autowired
private Dog requestDog;
@Autowired
private Dog sessionDog;
@Autowired
private ApplicationContext applicationContext;
@RequestMapping("/single")
public String single(){
Dog contextDog = (Dog)applicationContext.getBean("singleDog");
return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/prototype")
public String prototype(){
Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");
return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/request")
public String request(){
Dog contextDog = (Dog)applicationContext.getBean("requestDog");
return "dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/session")
public String session(){
Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
return "dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/application")
public String application(){
Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
return "dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();
}
}
单例作用域:http://127.0.0.1:8080/single
多次访问,得到的都是同一个对象,并且 @Autowired 和 applicationContext.getBean() 也是同一个对象。

多例作用域:http://127.0.0.1:8080/prototype
观察 ContextDog,每次获取的对象都不一样(而注入的对象 dog 在 Spring 容器启动时,就已经注入了,所以多次请求也不会发生变化)

请求作用域:http://127.0.0.1:8080/request
在一次请求中,@Autowired 和 applicationContext.getBean() 也是同一个对象。
但是每次请求,都会重新创建对象

会话作用域:http://127.0.0.1:8080/session
在一个session中,多次请求,获取到的对象都是同一个。
换一个浏览器访问,发现会重新创建对象。(另一个Session)

Application作用域:http://127.0.0.1:8080/application
在一个应用中,多次访问都是同一个对象

singleton |
application |
|
|---|---|---|
| 生命周期 | Spring 容器级(随容器启动) | ServletContext 级(Web 应用级别) |
| 管理者 | Spring IoC 容器 | Web 容器(Tomcat/Jetty) |
| 存储位置 | Spring 的单例缓存池(Map) | ServletContext#setAttribute() |
| 可访问范围 | Spring 管理的类之间 | 整个 Web 应用中共享 |
| 是否依赖 Web 环境 | ❌ 否(通用) | ✅ 是(仅限 Web 项目) |
| 被 Spring 管理? | ✅ 是 | ✅ 是,但底层基于 Web 容器 |
- Web 容器 (比如 Tomcat、Jetty)
- 是运行环境,负责管理和调度所有部署进去的 Web 应用。
- 它 "包含" 多个 Web 应用,每个 Web 应用是一个独立的运行单元
- Web 容器本身不直接管理 Spring 容器,但它提供了 Web 应用运行所需的基础设施(线程池、Servlet 容器、
ServletContext等)。
- Web 应用
- 是部署在 Web 容器中的一个具体应用(比如你的商城、博客系统)。
- 它包含了应用的代码、资源和配置。
- 在 Web 应用启动过程中,会创建和初始化 Spring 容器(WebApplicationContext)。
- Web 应用通过配置(比如
web.xml中的ContextLoaderListener或 Spring Boot 的启动类)来启动 Spring 容器。
- Spring 容器 (
ApplicationContext/WebApplicationContext)- 是 Web 应用中的一部分,专门负责管理 Bean、依赖注入、生命周期等。
- Spring 容器依赖 Web 应用提供的环境(比如可以通过
ServletContext获得一些信息)。 - Spring 容器和 Web 应用相互配合:
- Spring 管理业务逻辑和组件;
- Web 应用提供 Web 运行时环境(HTTP 请求、Session 等);
🧩 类比一下:
- Web 容器是大楼物业;
- Web 应用是大楼里的租户办公室;
- Spring 容器是办公室里的智能管理系统,帮租户高效管理员工(Bean)和资源。
💥通常情况下,一个 Web 应用 应该只拥有一个 Spring 容器 实例 (ApplicationContext) ,并且 Spring 的底层设计就是这么保障的,以避免 Bean 冲突、上下文混乱等问题。
Ⅱ. Bean的生命周期
生命周期指的是一个对象从诞生到销毁的整个生命过程,这个过程就叫做一个对象的生命周期。
Bean的生命周期分为以下5个部分:
- 实例化 (为 Bean 分配内存空间,即 构造方法)
- 属性赋值 (Bean 的注入 和装配,比如
@AutoWired) - 初始化
- 执行各种通知,如
BeanNameAware、BeanFactoryAware、ApplicationContextAware的接口方法。 - 执行初始化方法
- xml 定义
init-method - 使用注解的方式
@PostConstruct - 执行初始化后置方法
BeanPostProcessor
- xml 定义
- 执行各种通知,如
- 使用Bean
- 销毁Bean
- 销毁容器的各种方法,如
@PreDestroy、DisposableBean接口方法、destroy-method。
- 销毁容器的各种方法,如
比如我们现在需要买一栋房子,那么我们的流程是这样的:
- 先买房(实例化,从无到有)
- 装修(设置属性)
- 买家电,如洗衣机,冰箱,电视,空调等(各种初始化,可以入住)
- 入住(使用Bean)
- 卖房(Bean销毁)
执行流程如下图所示:

源码阅读
创建Bean的代码入口在 AbstractAutowireCapableBeanFactory#createBean
java
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//...代码省略
try {
// 在实例化之前, 是否有快捷创建的Bean, 也就是通过PostProcessorsBeforeInstantiation返回的Bean
// 如果存在, 则会替代原来正常通过target bean生成的bean的流程
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 创建Bean
// 方法中包含了实例化、属性赋值、初始化过程
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
//...代码省略
}
}
点进去继续看源码:AbstractAutowireCapableBeanFactory#doCreateBean
java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 创建bean实例
//...代码省略
if (instanceWrapper == null) {
// 实例化Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//...代码省略
//初始化bean实例
Object exposedObject = bean;
try {
// 依据bean definition 完成bean属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 执行bean初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException &&
beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
//...代码省略
return exposedObject;
}
这三个方法与三个生命周期阶段一一对应
createBeanInstance():实例化populateBean():属性赋值initializeBean():初始化
继续点进去 initializeBean():
java
// 初始化Bean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
// 调用的三个Bean开头的Aware方法
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 调用初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
// 调用的三个Bean开头的Aware方法
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
Ⅲ. SpringBoot自动配置
SpringBoot 的自动配置就是当 Spring 容器启动后,一些配置类、bean 对象等就自动存入到了 IoC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
一、Spring 加载 Bean
1. 问题描述
需求:使用 Spring 管理第三方的 jar 包的配置
引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入

第三方文件代码:
java
@Component
public class MyConfig {
public void func() {
System.out.println("start func() ...");
}
}
获取 MyConfig 这个 Bean:
java
@SpringBootTest
class BeanTheoryApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
MyConfig myConfig = applicationContext.getBean(MyConfig.class);
System.out.println(myConfig);
}
}
运行后报错:

2. 原因分析
Spring 通过五大注解和 @Bean 注解可以帮助我们把 Bean 加载到 Spring IoC 容器中,以上有个前提就是这些注解类需要和 SpringBoot 启动类在同一个目录下 (@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类)
当我们引入第三方的 Jar 包时,第三方的 Jar 代码目录肯定不在启动类的目录下,如何告诉 Spring 帮我们管理这些 Bean 呢?
3. 解决方案
常见的解决方案有两种:
@ComponentScan组件扫描@Import导入(使用@Import导入的类会被Spring加载到IoC容器中)
① @ComponentScan
在启动类中添加扫描路径:
java
@ComponentScan("com.liren.third")
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}
也可以指定扫描多个包:
java
@ComponentScan({"com.liren.autoconfig", "com.example.demo"})
Spring是否使用了这种方式呢?非常明显,没有。(因为我们引入第三方框架时,没有加扫描路径。比如mybatis)
如果
SpringBoot采用这种方式,当我们引入大量的第三方依赖,比如Mybatis、jackson等时,就需要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。
② @Import
@Import 导入主要有以下几种形式:
- 导入类
- 导入
ImportSelector接口实现类
1. 导入类
java
@Import(MyConfig.class)
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}
也可以采用导入多个类:
java
@Import({MyConfig1.class, MyConfig2.class})
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}
很明显,这种方式也很繁琐,所以 SpringBoot 依然没有采用这种方式!
2. 导入 ImportSelector 接口实现类
首先写一个 ImportSelector 接口实现类,重写里面的 selectImports() 方法:
java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.liren.third.MyConfig"};
}
}
启动类中导入我们自定义的 MyImportSelector:
java
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
如何解决上述两种方式的问题❓
这两种方式都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些 Bean 对象或配置类。如果漏掉其中一些 Bean,很可能导致我们的项目出现大的事故。
依赖中有哪些 Bean,使用时需要配置哪些 Bean,第三方依赖最清楚,那能否由第三方依赖来做这件事呢?
- 比较常见的方案就是第三方依赖给我们提供一个注解 ,这个注解一般都以
@EnableXxxx开头的注解,注解中封装的就是@Import注解
-
第三方依赖提供注解:注解中封装
@Import注解,导入MyImportSelector.classjava@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyConfig.class) public @interface EnableMyConfig { } -
在启动类上使用第三方提供的注解
java
@EnableMyConfig
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}
这种方式也可以导入第三方依赖提供的 Bean,并且这种方式更优雅一点,SpringBoot 采用的也是这种方式。
二、自动装配原理
1. 源码阅读
一切的来自起源 SpringBoot 的启动类开始:(@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类)
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// 获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
// 从Spring上下文中获取对象
BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
beanLifeComponent.use();
}
}
这个类和普通类唯一的区别就是 @SpringBootApplication 注解,这个注解也是 SpringBoot 实现自动配置的核心。
这个注解的内容如下所示:

- JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为meta-annotation(元注解),它们分别是:
@Target:描述注解的使用范围(即被修饰的注解可以用在什么地方)@Retention:描述注解保留的时间范围@Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息@Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)
2. @EnableAutoConfiguration 详解
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
① @Import({AutoConfigurationImportSelector.class})
- 使用
@Import注解,导入了实现ImportSelector接口的实现类:
java
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取自动配置的配置类信息
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
selectImports() 方法底层调用 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合:
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取在配置文件中配置的所有自动配置类的集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations(...) 方法获取在配置文件中配置的所有自动配置类的集合:
java
// 获取所有基于
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
// META-INF/spring.factories文件中配置类的集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found in " + "META-INF/spring/"
+ this.autoConfigurationAnnotation.getName() + ".imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
获取所有基于 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(在 IDEA 中双击 shift 之后打开查看)、META-INF/spring.factories 文件中配置类的集合。
在引入的起步依赖中,通常都有包含以上两个文件:

- 在加载自动配置类的时候,并不是将所有的配置全部加载进来 ,而是通过
@Conditional等注解的判断进行动态加载。@Conditional是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
META-INF/spring.factories文件是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件中配置即可,Spring就会把相应的实现类注入到容器中。
比如 Redis 的配置:RedisAutoConfiguration

可以看到,配置文件中使用 @Bean 声明了一些对象,spring 就会自动调用配置类中使用 @Bean 标识的方法,并把对象注册到 Spring IoC 容器中。
② @AutoConfigurationPackage
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class
java
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
Registrar实现了ImportBeanDefinitionRegistrar类,就可以被注解@Import导入到spring容器里。
其中 (new PackageImports(metadata)).getPackageNames().toArray(new String[0]) 表示当前启动类所在的包名。
结论: @AutoConfigurationPackage 就是将启动类所在的包下面所有的组件都扫描注册到 spring 容器中 。
3. 总结
SpringBoot 自动配置原理的大概流程如下:

当 SpringBoot 程序启动时,会加载配置文件当中所定义的配置类,通过 @Import 注解将这些配置类全部加载到 Spring 的 IOC 容器中,交给 IOC 容器管理。
