Ⅰ. 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,需要搭配不同的注解,如下所示:
@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取到的对象是否一样:
@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
-
-
-
使用Bean
-
销毁Bean
- 销毁容器的各种方法,如
@PreDestroy、DisposableBean接口方法、destroy-method。
- 销毁容器的各种方法,如
比如我们现在需要买一栋房子,那么我们的流程是这样的:
先买房(实例化,从无到有)
装修(设置属性)
买家电,如洗衣机,冰箱,电视,空调等(各种初始化,可以入住)
入住(使用Bean)
卖房(Bean销毁)
执行流程如下图所示:

源码阅读
创建Bean的代码入口在 AbstractAutowireCapableBeanFactory``#``createBean
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
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():
// 初始化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 包的配置
引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入

第三方文件代码:
@Component
public class MyConfig {
public void func() {
System.*out*.println("start func() ...");
}
}获取 MyConfig 这个 Bean:
@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
在启动类中添加扫描路径:
**@ComponentScan("com.liren.third")**
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.*run*(BeanTheoryApplication.class, args);
}
}也可以指定扫描多个包:
@ComponentScan({"com.liren.autoconfig", "com.example.demo"})
Spring是否使用了这种方式呢?非常明显,没有。 (因为我们引入第三方框架时,没有加扫描路径。比如mybatis)
如果
SpringBoot采用这种方式,当我们引入大量的第三方依赖,比如Mybatis、jackson等时,就需要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。
② @Import
@Import 导入主要有以下几种形式:
-
导入类
-
导入
ImportSelector接口实现类
1. 导入类
**@Import(MyConfig.class)**
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.*run*(BeanTheoryApplication.class, args);
}
}也可以采用导入多个类:
@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() 方法:
public class MyImportSelector **implements ImportSelector** {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{**"com.liren.third.MyConfig"** };
}
}启动类中导入我们自定义的 MyImportSelector:
**@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.class@Retention(RetentionPolicy.*RUNTIME*) @Target(ElementType.*TYPE*) **@Import(MyConfig.class)** public @interface EnableMyConfig { } -
在启动类上使用第三方提供的注解
**@EnableMyConfig**
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.*run*(BeanTheoryApplication.class, args);
}
}这种方式也可以导入第三方依赖提供的 Bean,并且这种方式更优雅一点,SpringBoot 采用的也是这种方式。
二、自动装配原理
1. 源码阅读
一切的来自起源 SpringBoot 的启动类开始:(@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类)
**@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 详解
@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 接口的实现类:
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() 方法,获取可自动配置的配置类信息集合:
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(...) 方法获取在配置文件中配置的所有自动配置类的集合:
// 获取所有基于
// 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
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
**@Import({AutoConfigurationPackages.Registrar.class})**
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class
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 容器管理。