目录
解决流程实例:(a依赖b,b依赖a,均为单例+setter注入)
SpringBean的初始化过程
SpringBean的初始化过程是Spring容器创建和管理Bean的核心流程,涉及多个阶段和扩展点。以下是完整过程:
1.容器启动阶段:
(1)加载配置:Spring 容器(如ApplicationContext)加载配置元数据(XML、注解、Java 配置类等),解析出 Bean 的定义信息(BeanDefinition),包括类名、作用域、依赖关系、初始化方法等。
(2)注册 Bean 定义:将解析出的BeanDefinition注册到容器的BeanDefinitionRegistry中,此时还未实例化 Bean。
2.Bean实例化:(当容器首次请求Bean(如调用getBean())或触发懒加载时,开始实例化)
(1)选择构造器:根据 Bean 定义,容器选择合适的构造器(默认无参构造器,或通过@Autowired/XML 配置指定的构造器)。
(2)创建实例:通过反射调用构造器,生成 Bean 的原始对象(尚未设置属性)。
3.属性注入:
(1)依赖解析:根据 Bean 定义中的依赖关系(如@Autowired、@Resource或 XML 的<property>),容器查找并获取依赖的 Bean。
(2)注入依赖:将依赖的 Bean 通过 setter 方法、字段注入或构造器参数注入到当前 Bean 中。若使用自动装配(如byName/byType),容器会按规则自动匹配依赖。
4.初始化前:(先执行前置处理)
(1)Aware 接口回调:若 Bean 实现了Aware系列接口(如BeanNameAware、BeanFactoryAware、ApplicationContextAware),容器会调用对应的方法注入相关信息:
BeanNameAware:注入当前 Bean 的 id/name;
BeanFactoryAware:注入当前 BeanFactory 容器;
ApplicationContextAware:注入当前 ApplicationContext 容器。
(2)BeanPostProcessor 前置处理:容器调用所有BeanPostProcessor的postProcessBeforeInitialization方法,可对 Bean 进行修改(如 AOP 代理前置处理)。
5.初始化:(执行自定义初始化逻辑)
(1)PostConstruct注解方法:若 Bean 的方法标注了@PostConstruct,容器会在此时调用(JSR-250 规范)。
(2)初始化方法:
若 XML 配置中指定了init-method属性(如<bean init-method="init"/>);
或 Bean 实现了InitializingBean接口,调用其afterPropertiesSet()方法;
容器会执行这些方法(afterPropertiesSet优先级高于init-method)。
6.初始化后:
BeanPostProcessor 后置处理:容器调用所有BeanPostProcessor的postProcessAfterInitialization方法,此处是 AOP 动态代理的关键节点(如生成代理对象替换原始 Bean)。
7.Bean就绪:
经过上述步骤后,Bean 已完全初始化,放入容器的缓存中,供后续使用(单例 Bean 会一直存在于容器中,原型 Bean 则在每次请求时重新创建)。
8.销毁阶段:(当容器关闭时(如ApplicationContext.close()),单例 Bean 会执行销毁逻辑)
(1)@PreDestroy注解方法:调用标注@PreDestroy的方法(JSR-250 规范)。
(2)销毁方法:
若 XML 配置中指定了destroy-method属性;或 Bean 实现了DisposableBean接口,调用其destroy()方法;容器会执行这些销毁方法(destroy优先级高于destroy-method)。
Bean的五种作用域
正常spring框架支持两个:
singleton(默认);
prototype
在web项目中,会增加后三个:
singleton:使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。
prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。
request:该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
session:该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。
global-session:该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
Bean的五种自动装配(通过配置文件)
自动装配的核心配置:autowire属性
1.byName(按属性名匹配)
原理:Spring容器会查找目标bean的属性名与容器中其它bean的id值完全一致的依赖,找到后自动注入。目标bean的属性必须提供setter方法(因为注入过程本质是调用setter方法赋值)
定义Java类&XML配置文件
2.byType(按属性类型匹配)
原理:spring容器会查找目标bean的属性类型与容器中其他的bean的class类型(或子类,实现类)一致的依赖,找到后自动注入。目标bean的属性必须提供setter方法;容器中同类型的bean只能有一个。
3.Constructor(按构造器参数类型/名称匹配)
原理:spring容器会查找与目标bean构造器参数类型(或参数名,优先级低于类型)匹配的bean,自动注入到构造器中,完成bean的初始化。目标bean必须提供带参数的构造器(无参构造器可选,但推荐保留,避免spring初始化异常)
修改UserService&XML配置文件
4.Default(继承父容器的自动装配原则)
原理:default不是独立的装配模式,而是“继承上级配置”的规则:
若在<beans>标签中通过default-autowire属性设置了全局自动装配模式(如default-autowire="byType"),则autowire="default"的 bean 会继承该全局模式;
若<beans>标签未设置default-autowire,则default等价于no(不自动装配)。
配置全局自动装配模式
5.No(不自动装配,默认值)
原理:autowire="no"表示关闭自动装配,所有依赖必须通过配置文件显式指定(如<property>标签),是 Spring 的默认行为。
XML配置文件
Spring里的循环依赖问题
循环依赖的三种场景:
1.构造器注入循环依赖:两个 Bean 通过构造器相互依赖(如 A 的构造器需要 B,B 的构造器需要 A)。Spring 无法解决,会直接抛出异常。
2.Setter注入(或字段注入)的单例Bean循环依赖:两个单例 Bean 通过 setter 方法或字段注入相互依赖。Spring 可以解决,是最常见的可处理场景。
3.原型Bean循环依赖:原型(prototype)作用域的 Bean 之间的循环依赖(无论注入方式)。Spring 无法解决,会抛出异常。
Spring解决单例Bean循环依赖的核心原理:
Spring通过三级缓存机制解决单例Bean的循环依赖,核心思想:提前暴露未初始化完成的Bean,让依赖方可以引用。
三级缓存的定义:
Spring 在DefaultSingletonBeanRegistry中维护了三个缓存(Map):
一级缓存(singletonObjects):存储完全初始化完成的单例 Bean(最终可用的 Bean)。
二级缓存(earlySingletonObjects):存储提前暴露的未完全初始化的单例 Bean(仅实例化但未注入属性和执行初始化方法)。
三级缓存(singletonFactories):存储Bean 工厂对象(ObjectFactory),用于在需要时创建 Bean 的早期引用(可能是原始对象或代理对象)。
解决流程实例:(a依赖b,b依赖a,均为单例+setter注入)
1.初始化 A:
实例化 A(调用构造器创建原始对象)。
将 A 的工厂对象(ObjectFactory)放入三级缓存(singletonFactories),此时 A 尚未注入属性。
2.A 需要注入 B:
容器检查一级缓存,发现 B 不存在,开始初始化 B。
3.初始化 B:
实例化 B(调用构造器创建原始对象)。
将 B 的工厂对象放入三级缓存。
B 需要注入 A:容器检查一级缓存(A 不存在)→ 检查二级缓存(A 不存在)→ 从三级缓存获取 A 的工厂对象,生成 A 的早期引用,放入二级缓存(earlySingletonObjects),并删除三级缓存中的 A 工厂。
B 注入 A 的早期引用,完成属性注入和初始化,放入一级缓存(singletonObjects)。
4.A 完成初始化:
容器从一级缓存获取 B,注入 A 中。
A 完成初始化,放入一级缓存,删除二级缓存中的 A。
无法解决的循环依赖场景及原因:
1.构造器注入循环依赖:构造器注入要求依赖在 Bean 实例化阶段(调用构造器时)就必须存在,而此时依赖的 Bean 可能尚未实例化,无法提前暴露,导致循环等待。
2.原型Bean循环依赖:原型 Bean 每次请求都会重新创建,且 Spring 不缓存原型 Bean,无法提前暴露未初始化的实例,导致无限循环创建。
如何解决/避免循环依赖:
1.优先使用setter注入/字段注入,避免构造器注入的循环依赖;
2.重构代码消除循环依赖,从设计层面解耦,将共同依赖的逻辑抽取到第三方组件中,打破闭环;
3.使用@lazy注解(针对构造器注入),延迟依赖Bean的初始化(本质是创建代理对象暂时替代);
4.使用@DependsOn指定初始化顺序(适用于非直接依赖的场景)。