spring的核心思想说说你的理解?
Spring的核心思想主要体现在两个方面:IoC(控制反转) 和 AOP(面向切面编程)。
1. IoC(控制反转):
- 核心理念:把对象的创建、依赖的注入以及整个生命周期的管理,从程序员手中"反转"给Spring容器来负责。
- 解决的问题 :传统开发中,对象之间层层
new
,导致代码高度耦合,难以维护和测试。IoC通过"依赖注入"(DI)的方式,让容器主动把依赖的对象"注入"进来,而不是由对象自己去创建。这极大地降低了组件间的耦合度。2. AOP(面向切面编程):
- 核心理念:将那些散布在多个业务模块中、与核心业务逻辑无关的通用功能(如日志、事务、权限、缓存等)抽离出来,形成独立的"切面"(Aspect)。
- 解决的问题:避免在每个业务方法中都重复编写相同的非业务代码,实现"关注点分离"。让开发者可以专注于核心业务逻辑,同时又能方便地添加横切功能。
- 实现方式:通过动态代理(JDK或CGLIB),在程序运行时将切面代码"织入"到目标方法的指定位置(如方法前、后、异常时等)。
总结来说 ,Spring的核心思想就是通过 IoC 来管理对象和解耦,通过 AOP 来增强功能和复用代码。两者相辅相成,共同构建了一个松耦合、高内聚、易于扩展和维护的企业级应用开发框架。它让开发者从繁琐的对象管理和重复代码中解放出来,更专注于业务价值的实现。
什么是IoC (Inversion of Control - 控制反转)
核心思想 :将对象的创建、依赖关系的管理以及整个生命周期的控制权,从应用程序代码中"反转"交给 Spring 容器来负责。开发者不再需要在代码里使用
new
关键字来创建对象。具体实现:依赖注入 (DI - Dependency Injection)。
- Spring 容器会根据配置(XML、注解或 Java 配置)来管理所有被称为 Bean 的对象。
- 当一个 Bean 需要依赖另一个 Bean 时(例如
UserService
需要UserRepository
),它不需要自己去创建UserRepository
的实例。- 开发者通过
@Autowired
、@Resource
等注解或配置来声明依赖关系。- Spring 容器会在运行时自动查找、创建并"注入"所需的依赖对象。
带来的好处:
- 降低耦合度:组件之间通过接口或抽象依赖,而不是具体实现,使得代码更加灵活。
- 易于测试:可以轻松地使用 Mock 对象替换真实的依赖,进行单元测试。
- 易于维护和扩展:修改依赖关系或替换实现类时,通常只需要修改配置,而不需要改动大量业务代码。
- 集中管理:所有对象的创建和管理都集中在容器中,便于统一配置和监控。
什么是AOP (Aspect-Oriented Programming - 面向切面编程)
核心思想 :将那些横切关注点(Cross-Cutting Concerns)------ 即那些散布在多个业务模块中、与核心业务逻辑无关但又必须执行的通用功能(如日志记录、事务管理、安全检查、性能监控)------ 从各个业务方法中抽离出来,形成独立的"切面"(Aspect)。
具体实现:动态代理。
- Spring AOP 在运行时,会为目标对象创建一个代理对象。
- 当调用目标对象的方法时,实际上是调用了代理对象的方法。
- 代理对象可以在目标方法执行的特定连接点 (Join Point,通常是方法执行)上,织入(Weave)切面中的代码。
- 这些特定的时机被称为通知 (Advice),例如:
@Before
: 方法执行前@After
: 方法执行后(无论是否异常)@AfterReturning
: 方法成功返回后@AfterThrowing
: 方法抛出异常后@Around
: 环绕方法,可以控制是否执行、修改参数和返回值。带来的好处:
- 关注点分离:核心业务代码专注于业务逻辑本身,横切功能被独立管理。
- 代码复用:通用功能只需编写一次,就可以应用到多个地方。
- 减少重复代码:避免了在每个业务方法中都写相同的日志或事务代码。
- 提高可维护性:修改日志格式或事务策略时,只需修改切面代码,而不需要修改所有业务方法。
请介绍一下JDK动态代理和CGLIB动态代理
JDK动态代理:
- 实现原理:基于Java的反射机制,要求目标类必须实现至少一个接口。Spring会在运行时动态创建一个实现了与目标类相同接口的代理类。
- 核心组件 :
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。通过Proxy.newProxyInstance()
方法创建代理对象,所有方法调用都会被InvocationHandler
的invoke()
方法拦截。- 优点:是Java原生支持,无需额外依赖。
- 缺点:只能代理实现了接口的类。
CGLIB动态代理:
- 实现原理 :基于字节码生成库(ASM),通过继承目标类来创建子类作为代理。代理类会重写目标类的所有非
final
方法。- 核心组件 :
net.sf.cglib.proxy.Enhancer
类和net.sf.cglib.proxy.MethodInterceptor
接口。通过Enhancer
设置目标类和拦截器,生成代理对象。- 优点:可以代理没有实现接口的类,适用范围更广。
- 缺点 :需要引入CGLIB库,对
final
类和final
方法无法代理。
Spring IOC 的实现机制
Spring的loC(控制反转)实现机制,简单来说就是通过工厂模式 和反射机制 ,把对象的创建和依赖关系的管理交给Spring容器来处理。这样开发者就不用自己手动去new对象,也不用自己维护依赖关系,只需要在配置文件或者注解里告诉Spring"我需要哪些Bean,它们之间是什么关系",Spring就会自动帮你创建好并组装好这些对象。
这就像你只需要告诉厨房"我要一份炒菜,里面有肉和青菜",厨师(Spring容器)就会帮你准备好食材、切好、炒好,你直接吃就行,不用自己去洗菜切菜炒菜
具体流程
配置元信息读取:
- Spring 容器启动时,会读取配置元信息。这些信息可以来自 XML 文件、Java 注解(如
@Component
,@Service
,@Repository
,@Controller
)或 Java 配置类(@Configuration
)。- 这些配置告诉容器:"有哪些 Bean 需要管理,它们的依赖关系是什么"。
Bean 定义注册:
- 容器将读取到的配置信息解析并封装成
BeanDefinition
对象。BeanDefinition
就像是一个"蓝图",它包含了 Bean 的类名、作用域(singleton/prototype)、生命周期回调方法、属性值、依赖的其他 Bean 名称等所有创建和管理 Bean 所需的信息。- 这些
BeanDefinition
被注册到一个BeanDefinitionRegistry
(本质上是一个Map
)中,以 Bean 的名称为 key。Bean 实例化:
- 当需要获取一个 Bean 时(例如通过
getBean()
或依赖注入),容器会根据BeanDefinition
中的类名,使用 Java 反射 (Class.forName()
和newInstance()
,或构造函数反射)来创建该类的实例。- 对于单例(Singleton)Bean,这个实例化过程通常在容器启动时就完成了(预实例化)。
依赖注入(DI):
- 实例化后,容器会检查该 Bean 的
BeanDefinition
,看它依赖了哪些其他 Bean。- 容器会从自己的"工厂"里查找(或创建)这些依赖的 Bean。
- 然后通过 反射 调用 setter 方法(
@Autowired
on setter/field)或直接设置字段值(@Autowired
on field),或者通过构造函数注入(@Autowired
on constructor),将依赖的对象"注入"到当前 Bean 中。Bean 生命周期管理:
- 在 Bean 的创建过程中,容器会执行一系列生命周期回调:
- 初始化前 :调用
BeanPostProcessor
的postProcessBeforeInitialization
方法。- 初始化 :调用
@PostConstruct
注解的方法,或InitializingBean
的afterPropertiesSet()
方法,或在BeanDefinition
中指定的init-method
。- 初始化后 :调用
BeanPostProcessor
的postProcessAfterInitialization
方法。AOP 代理通常在这里生成。- 对于单例 Bean,创建好的实例会被放入单例缓存池(一级缓存)中,后续请求直接返回该实例。
- 容器关闭时,会调用
@PreDestroy
、DisposableBean
的destroy()
或destroy-method
来清理资源。总结来说,Spring IoC 容器就像一个超级智能的"对象工厂":
- 读取蓝图 (
BeanDefinition
)- 按图索骥(反射创建实例)
- 组装零件(依赖注入)
- 质量检查(生命周期回调)
- 入库待取(放入缓存)
它通过反射 技术实现了对象的动态创建和属性的动态设置,通过工厂模式统一管理对象的生命周期,最终实现了控制反转,让开发者从繁琐的对象管理中解放出来。
Spring AOP 实现机制
Spring AOP 的实现机制主要依赖于动态代理技术,在运行时为需要增强的目标对象创建一个代理对象,从而在不修改原始代码的情况下,将横切逻辑(切面)织入到业务流程中。
识别切面和目标:
- Spring 容器启动时,会扫描所有被
@Aspect
注解标记的类,识别出切面(Aspect)。- 同时,容器会根据切面中定义的切入点 (Pointcut)表达式,找到所有匹配的目标对象(Target Object)。
创建代理对象:
- 对于每一个需要被 AOP 增强的目标对象,Spring 会创建一个代理对象(Proxy)。这个代理对象会替代原始的目标对象被注入到其他 Bean 中。
- Spring 选择代理策略遵循以下规则:
- JDK 动态代理 :如果目标对象实现了至少一个接口 ,Spring 会使用 JDK 的
java.lang.reflect.Proxy
机制,创建一个实现了与目标对象相同接口的代理类。代理类会持有目标对象的引用,并在调用接口方法时进行拦截。- CGLIB 动态代理 :如果目标对象没有实现任何接口 ,Spring 会使用 CGLIB 库,通过继承 目标类的方式创建一个子类作为代理。这个子类会重写目标类的所有非
final
方法,并在这些方法中插入拦截逻辑。织入通知:
- 代理对象中包含了切面的通知(Advice)逻辑。
- 当客户端代码调用目标方法时,实际上是调用了代理对象的方法。
- 代理对象会根据配置的通知类型(
@Before
,@After
,@Around
等),在调用真正的目标方法之前 、之后 或环绕执行切面中定义的额外逻辑。- 例如,一个
@Around
通知可以用来开启事务,然后调用目标方法,最后根据结果提交或回滚事务。运行时织入:
- Spring AOP 的织入(Weaving)发生在运行期,而不是编译期或类加载期。这是它与 AspectJ(一种更强大的 AOP 框架)的主要区别之一。
- 代理对象是在 Bean 创建过程中,通过
BeanPostProcessor
(特别是AbstractAutoProxyCreator
)在 Bean 初始化后生成的。总结 : Spring AOP 的实现机制可以概括为:在运行时,基于目标对象是否实现接口,选择使用 JDK 动态代理或 CGLIB 动态代理,为目标对象创建一个代理。这个代理对象负责拦截方法调用,并在调用目标方法的前后(或周围)执行切面中定义的通知逻辑,从而实现功能的增强和关注点的分离。 这种方式对业务代码是无侵入的,开发者只需关注业务逻辑和切面的定义。
怎么理解SpringIoc?
IOC:Inversion Of Control,即控制反转,是一种设计思想。在传统的Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象;而在Spring程序设计中,IOC 是有专门的容器去控制对象。
所谓控制就是对象的创建、初始化、销毁。
- 创建对象:原来是 new 一个,现在是由 Spring 容器创建。
- 初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。
- 销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。
总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。
所谓反转 :其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。
依赖注入?怎么实现依赖注入的?
依赖注入(Dependency Injection, DI)是 Spring IoC 容器的核心功能,它指的是将一个对象所依赖的其他对象,由外部容器(Spring)来创建并注入,而不是由对象自己内部去创建。
依赖注入的实现方式主要有三种:
基于构造函数的注入(Constructor-based Injection):
- 通过类的构造函数来注入依赖。
- 在 Spring 配置中,容器会查找带有
@Autowired
注解的构造函数(在 Spring 4.3+ 之后,如果类只有一个构造函数,可以省略@Autowired
),然后根据参数类型去容器中查找匹配的 Bean,并在实例化该类时,将这些 Bean 作为参数传入构造函数。- 优点:依赖关系清晰,对象创建后依赖不可变,有利于实现不可变对象,推荐使用。
基于 Setter 方法的注入(Setter-based Injection):
- 通过类的 setter 方法来注入依赖。
- 容器会先调用无参构造函数创建 Bean 实例,然后调用带有
@Autowired
注解的 setter 方法,将依赖的 Bean 作为参数传入。- 优点:灵活性高,允许在对象创建后重新设置依赖。
- 缺点:对象可能处于部分初始化状态,依赖关系不如构造函数注入清晰。
基于字段的注入(Field-based Injection):
- 直接在类的字段上使用
@Autowired
注解。- 容器会通过 Java 反射 (
setAccessible(true)
和Field.set()
)直接修改对象的私有字段值,将其设置为容器中匹配的 Bean。- 优点:代码简洁,写法最简单。
- 缺点 :破坏了封装性,不利于单元测试(难以在测试中替换依赖),对象依赖关系不透明,且容易产生循环依赖问题。不推荐使用。
动态代理和静态代理的区别
特性 静态代理 动态代理 生成时机 编译期(提前写好) 运行时(动态生成) 灵活性 低,通常一对一 高,可一对多 代码量 多,容易产生大量重复代码 少,通用逻辑集中 耦合度 高 低 实现方式 手动编码 JDK 反射 / CGLIB 字节码增强 Spring AOP 不使用 核心实现机制(JDK/CGLIB) 动态代理和静态代理的核心区别在于代理类的生成时机和灵活性。
1. 静态代理(Static Proxy):
- 定义 :在编译期就手动编写好代理类,代理类和被代理类(目标类)实现相同的接口或继承相同的父类。
- 特点 :
- 提前写好:代理类的代码是程序员提前写好的,是固定的。
- 一对一:一个静态代理类通常只为一个特定的目标类服务。如果要代理多个类,就需要编写多个对应的代理类,代码冗余度高。
- 耦合度高:代理逻辑和目标类是硬编码在一起的,不够灵活。
- 实现简单:概念直观,易于理解。
- 例子:就像你每次出门都雇一个固定的保镖,这个保镖只负责保护你一个人,而且你们之间的保护协议(规则)在你雇他时就已经定死了。
2. 动态代理(Dynamic Proxy):
- 定义 :在运行时由程序动态生成代理类的字节码,并创建代理对象。
- 特点 :
- 运行时生成:代理类不是提前写好的,而是在程序运行过程中,根据需要动态创建的。
- 一对多 :一个通用的代理逻辑(如
InvocationHandler
或MethodInterceptor
)可以代理多个不同的目标类,只要它们符合代理的条件(如实现接口或非final类)。- 高灵活性:可以灵活地为任何符合条件的类添加通用功能(如日志、事务),极大地减少了代码重复。
- 依赖技术:需要依赖反射(JDK)或字节码生成技术(CGLIB)。
- 例子:就像一个智能安保系统,它可以动态地为公司里任何员工(目标类)生成一个专属的虚拟保镖(代理对象),并且这个保镖的行为规则(如检查门禁、记录日志)是统一配置的,不需要为每个员工单独定制一套安保方案。
什么是动态代理
动态代理是一种在程序运行时动态地创建代理对象的技术,而不是在编译时就写好代理类。这个代理对象可以用来控制对真实目标对象的访问,并在调用目标对象方法的前后添加额外的逻辑(比如日志、事务、权限检查等)。
核心特点
- 运行时生成:代理类是在程序运行过程中,根据需要动态生成的,而不是在编码时手动编写的。
- 灵活性高:同一个代理逻辑可以适用于多个不同的目标类,只要它们符合一定的条件(如实现接口或非final类)。
- 解耦:业务逻辑和通用功能(如日志、事务)分离,提高了代码的复用性和可维护性。
在 Java 中,动态代理主要有两种实现:
JDK 动态代理:
- 原理:基于 Java 的反射机制,要求目标对象必须实现一个或多个接口。
- 实现 :通过
java.lang.reflect.Proxy
类和InvocationHandler
接口来创建代理。- 过程 :代理对象会实现与目标对象相同的接口。当调用代理对象的方法时,所有调用都会被转发到
InvocationHandler
的invoke()
方法中,在这里可以添加通用逻辑,再通过反射调用目标对象的真正方法。- 优点:Java 原生支持,无需额外依赖。
- 缺点:只能代理实现了接口的类。
CGLIB 动态代理:
- 原理:基于字节码生成库(ASM),通过继承目标类来创建代理。
- 实现 :使用
net.sf.cglib.proxy.Enhancer
类和MethodInterceptor
接口。- 过程 :CGLIB 会生成一个目标类的子类作为代理类,并重写其中的非
final
方法。在重写的方法中,会先调用拦截器的intercept()
方法来执行通用逻辑,然后再调用父类(即目标对象)的方法。- 优点:可以代理没有实现接口的类。
- 缺点 :需要引入 CGLIB 库,无法代理
final
类和final
方法。
什么是反射?有哪些使用场景?
反射(Reflection) 是 Java 的一种强大特性,它允许程序在运行时动态地获取类的信息(如类名、属性、方法、构造器等),并能操作这些信息,比如创建对象、调用方法、访问和修改字段值,即使在编写代码时并不知道这些类的具体细节。
主要功能
使用 Java 的
java.lang.reflect
包,反射可以做到:
- 获取类信息 :通过类名获取
Class
对象,进而得到类的完整信息。- 创建实例 :无需使用
new
,即可通过Class.newInstance()
或构造函数反射来创建对象。- 调用方法 :通过
Method
对象,动态调用对象的方法,包括私有方法(通过setAccessible(true)
)。- 访问字段 :通过
Field
对象,读取或修改对象的字段值,包括私有字段。- 获取注解:检查类、方法、字段上的注解信息。
常见的使用场景
比如开发框架时Spring 用它来注入依赖,
MyBatis用它映射 SQL 结果到对象;
写工具类时,JSON库用它把对象转成字符串;
甚至实现插件系统,让程序能加载外部类并实例化。
不过反射虽好,用多了会影响性能,毕竟"透视"总比直接操作费劲,所以一般只在需要动态灵活性的场景用它**。**
框架开发:
- Spring IoC/DI:Spring 容器就是靠反射来创建 Bean 实例,并通过 setter 或字段注入依赖的。没有反射,就无法实现控制反转。
- MyBatis / Hibernate:ORM 框架使用反射将数据库查询结果映射到 Java 对象(POJO)的字段上。它会根据实体类的属性名找到对应的表列,并通过 setter 方法或直接设置字段来填充数据。
- Spring MVC :处理请求时,通过反射调用控制器(Controller)中被
@RequestMapping
注解标记的方法。通用工具库:
- JSON 序列化/反序列化(如 Jackson, Gson):将 Java 对象转换成 JSON 字符串,或者将 JSON 解析成 Java 对象时,需要遍历对象的所有字段,这都依赖于反射。
- 对象拷贝工具 :像
BeanUtils
这样的工具,可以在不知道具体类的情况下,复制两个对象之间的同名属性,背后就是反射在工作。插件化系统:
- 程序可以在运行时加载外部的
.class
文件或 JAR 包,通过反射获取类信息并实例化对象,从而实现热插拔和功能扩展。单元测试和 Mock 框架:
- 像 JUnit 和 Mockito 这样的测试框架,经常需要访问私有方法或字段来进行测试,或者动态生成代理类来模拟(Mock)对象的行为,这都需要反射的支持。
注解处理:
- 很多基于注解的功能(如 Lombok 编译期处理除外)都需要在运行时通过反射来读取注解的值,并根据注解的配置执行相应的逻辑。
在Spring中循环依赖问题的三种情况
1. 构造器注入循环依赖(Constructor-based Circular Dependency)
- 场景:两个或多个 Bean 通过构造函数相互依赖。
java@Component public class A { public A(B b) { } // A 的构造函数需要 B } @Component public class B { public B(A a) { } // B 的构造函数需要 A }
- Spring 能否解决 :❌ 不能解决。
- 原因:创建 Bean 需要先调用构造函数,而调用构造函数时就必须提供所有参数(依赖)。A 需要 B 才能创建,但 B 又需要 A 才能创建,形成了死锁。Spring 的三级缓存是在 Bean 实例化(构造函数执行完毕)后才开始使用的,而构造器注入在实例化阶段就需要依赖,因此无法提前暴露"半成品"来打破循环。
- 结果 :Spring 容器启动时会抛出
BeanCurrentlyInCreationException
异常。2.原型(Prototype)多例模式下的循环依赖(Prototype-based Circular Dependency)
- 场景 :两个或多个作用域为
prototype
的 Bean 相互依赖(无论是构造器注入还是 setter 注入)。
java@Component @Scope("prototype") public class A { @Autowired private B b; // A 依赖 B } @Component @Scope("prototype") public class B { @Autowired private A a; // B 依赖 A }
- Spring 能否解决 :❌ 不能解决。
- 原因:Spring 的三级缓存机制只针对单例 Bean 设计。对于原型 Bean,每次获取都会创建一个新实例,缓存无法复用。当 A 需要 B 时,Spring 会创建一个新的 B 实例,而这个 B 实例在注入 A 时,又会尝试创建一个新的 A 实例,从而导致无限递归创建,最终可能引发栈溢出(StackOverflowError)。
- 结果:虽然不会像构造器注入那样直接抛异常,但会导致无限创建对象,程序无法正常工作。
3. 单例模式下的 Setter/Field 注入循环依赖(Singleton Setter/Field-based Circular Dependency)
- 场景 :两个或多个单例 Bean 通过
@Autowired
注解的 setter 方法或字段相互依赖。
java@Component public class A { @Autowired private B b; // A 依赖 B } @Component public class B { @Autowired private A a; // B 依赖 A }
- Spring 能否解决 :✅ 可以解决。
- 原因 :这是 Spring 三级缓存机制设计的初衷。流程如下:
- 创建 A,实例化后(A 已
new
出来),将其ObjectFactory
放入三级缓存。- 注入 A 的属性时发现需要 B,于是暂停 A 的创建,转而去创建 B。
- 创建 B,实例化后,将其
ObjectFactory
放入三级缓存。- 注入 B 的属性时发现需要 A。此时从三级缓存获取 A 的
ObjectFactory
,调用getObject()
提前暴露 A 的早期引用(半成品),并放入二级缓存。- 将 A 的早期引用注入到 B 中,B 创建完成,放入一级缓存。
- 回到 A,将完整的 B 注入,A 创建完成,放入一级缓存。
- 关键:依赖注入发生在实例化之后,因此可以提前暴露"半成品"来打破循环。
spring是如何解决循环依赖的?
Spring 主要通过 三级缓存 机制来解决单例 Bean 的循环依赖问题,但这种解决方式有其特定的前提和限制。
Spring 的 IoC 容器为单例 Bean 的创建过程维护了三个 Map(缓存),它们共同协作来打破循环依赖的死锁:
一级缓存
singletonObjects
:
- 存放完全初始化好的单例 Bean。
- 这是最终的"成品仓库",当其他 Bean 需要依赖时,会优先从这里获取。
- 数据结构:
ConcurrentHashMap<String, Object>
二级缓存
earlySingletonObjects
:
- 存放已经实例化但尚未完成属性填充和初始化的"早期引用"(半成品 Bean)。
- 当一个 Bean 被提前暴露给其他 Bean 依赖时,它会暂时存放在这里。
- 数据结构:
ConcurrentHashMap<String, Object>
三级缓存
singletonFactories
:
- 存放一个对象工厂 (
ObjectFactory
),这个工厂封装了创建早期引用的逻辑。- 当一个 Bean 开始创建时,Spring 会先将一个能创建该 Bean 早期引用的工厂放入这里。
- 数据结构:
ConcurrentHashMap<String, ObjectFactory<?>>
工作流程(以 A 依赖 B,B 依赖 A 为例)
创建 A:
- Spring 开始创建 Bean A。
- 先调用
A
的构造函数,完成实例化(A 已经被new
出来了,但属性还未注入)。- 将一个能获取 A 的早期引用的工厂(
ObjectFactory
)放入三级缓存。- 开始为 A 注入属性,发现需要依赖 Bean B。
创建 B:
- Spring 发现需要创建 Bean B,于是暂停 A 的创建流程,转而去创建 B。
- 调用
B
的构造函数,完成实例化。- 将能获取 B 的早期引用的工厂放入三级缓存。
- 开始为 B 注入属性,发现需要依赖 Bean A。
解决 B 对 A 的依赖(打破循环):
- B 需要 A,Spring 先从一级缓存找,没有(A 还没创建完)。
- 再从二级缓存找,也没有。
- 最后从三级缓存找到 A 对应的
ObjectFactory
。- 调用这个工厂的
getObject()
方法,提前暴露 A 的早期引用(此时的 A 是个"半成品",只有实例,属性还没注入)。- 将这个早期引用从工厂中取出,并放入二级缓存(避免重复创建)。
- 将 A 的早期引用注入到 B 中。
完成 B 的创建:
- B 成功注入了 A(虽然是个早期引用),继续完成 B 的其他初始化工作。
- B 创建完成后,从三级缓存移除其工厂,将其放入一级缓存。
完成 A 的创建:
- 回到 A 的创建流程,此时 B 已经创建完毕。
- 将 B 注入到 A 中。
- 继续完成 A 的初始化工作。
- A 创建完成后,从三级缓存移除其工厂,将其从二级缓存移到一级缓存。
Spring 通过 三级缓存 和 提前暴露未完全初始化的对象引用 的机制来解决单例作用域 Bean 的 sette注入方式的循环依赖问题。
Spring为什么用3级缓存解决循环依赖问题?用2级缓存不行吗?
用两级缓存理论上可以解决普通的循环依赖,但无法完美支持 AOP 代理场景。
Spring 使用三级缓存的核心目的就是为了在解决循环依赖的同时,确保 AOP 代理能够正确生成。
举个例子:假设 Bean A 依赖 B,B 又依赖 A,且 A需要被动态代理(比如加了 @Transactiona1 )。如果只有二级缓存,当 B创建时去注入 A,拿到的是A的原始对象。但 A在后续初始化完成后才会生成代理对象,结果就是:B 拿着原始对象 A,而 Spring 容器里存的是代理对象 A -- 同一个 Bean 出现了两个不同实例,这直接违反了单例的核心约束。
三级缓存中的 obiectFactory就是解决这个问题的关键。它不是直接缓存对象,而是存了一个能生产对象的工厂。
当发生循环依赖时,调用这个工厂的 getobject()方法,这时 Spring 会智能判断:如果这个Bean 最终需要代理,就提前生成代理对象并放入二级缓存;如果不需要代理,就返回原始对象。
这样一来,B 注入的 A 就是最终形态(可能是代理对象),后续A初始化完成后也不会再创建新代理,保证了对象全局唯一。
简单说,三级缓存的本质是**"按需延迟生成正确引用**"。它既维持了 Bean 生命周期的完整性(正常流程在初始化后生成代理),又在循环依赖时特殊处理,避免逻辑矛盾。而二级缓存缺乏这种动态决策能力,因此无法替代三级缓存。
spring框架中都用到了哪些设计模式
1. 工厂模式 (Factory Pattern)
- 应用 :这是 Spring IoC 容器的核心。
BeanFactory
和ApplicationContext
都是典型的工厂。- 作用 :它们负责创建、配置和管理 Bean 对象。开发者不需要直接使用
new
来创建对象,而是通过工厂(容器)来获取,实现了对象创建的解耦。2. 单例模式 (Singleton Pattern)
- 应用 :Spring 中 Bean 的默认作用域就是
singleton
。- 作用 :容器确保一个 Bean 定义在整个应用上下文中只对应一个实例。这通过容器内部的缓存(如
singletonObjects
)来实现,保证了资源的高效利用。3. 代理模式 (Proxy Pattern)
- 应用:Spring AOP 的基石。无论是 JDK 动态代理还是 CGLIB 代理,都属于代理模式。
- 作用:为真实的目标对象创建一个代理对象,代理对象可以控制对目标对象的访问,并在调用前后添加额外逻辑(如事务、日志),而对客户端透明。
4. 模板方法模式 (Template Method Pattern)
- 应用 :
JdbcTemplate
、RestTemplate
、JmsTemplate
等各种Template
类。- 作用 :这些类定义了操作的骨架(如获取连接、执行SQL、处理异常、关闭连接),将变化的部分(如 SQL 语句、结果集映射)封装成回调方法(如
RowMapper
),由用户实现。这极大地简化了重复性代码。5. 适配器模式 (Adapter Pattern)
- 应用 :Spring MVC 中的
HandlerAdapter
。- 作用 :不同的控制器(Controller)可以有不同的实现方式(如实现
Controller
接口、使用@RequestMapping
注解)。HandlerAdapter
作为适配器,能够统一调用这些不同类型的处理器,屏蔽了它们的差异。6. 观察者模式 (Observer Pattern)
- 应用 :Spring 的事件驱动模型(
ApplicationEvent
和ApplicationListener
)。- 作用 :当容器发布一个事件(如
ContextRefreshedEvent
)时,所有监听该事件的监听器都会收到通知并执行相应逻辑,实现了组件间的松耦合通信。7. 装饰器模式 (Decorator Pattern)
- 应用 :
BeanWrapper
和PropertyEditor
的设计中有所体现。- 作用:可以在不改变原始对象的情况下,动态地为对象添加新的职责或功能,比如类型转换、属性访问等。
8. 策略模式 (Strategy Pattern)
- 应用 :资源加载(
ResourceLoader
)、数据源选择、视图解析(ViewResolver
)等。- 作用:定义了一系列可互换的算法或策略,客户端可以在运行时根据需要选择使用哪种策略,提高了系统的灵活性和可扩展性。
9. 原型模式 (Prototype Pattern)
- 应用 :当 Bean 的作用域被设置为
prototype
时。- 作用:每次请求该 Bean 时,容器都会创建一个新的实例,类似于通过原型克隆出新对象。
Spring 框架通过组合运用这些设计模式,构建了一个高度解耦、灵活且易于扩展的生态系统。理解这些设计模式,是深入掌握 Spring 原理的关键。例如,IoC 本质是工厂模式 + 单例模式 ,AOP 本质是代理模式 ,而各种
Template
类则是模板方法模式的经典实践。
spring 常用注解有什么?
Spring Boot01(注解、)---java八股-CSDN博客
Spring的事务什么情况下会失效?
Spring 事务在以下几种常见情况下会失效,导致
@Transactional
注解不起作用:1. 方法不是
public
的
- 原因 :Spring AOP 基于代理,默认情况下,
@Transactional
只能作用于public
方法。因为代理对象需要通过公共方法进行拦截。- 解决 :将方法声明为
public
。2. 自身调用(内部调用)
- 场景 :在一个类中,
public
方法 A 调用同一个类的@Transactional
方法 B。- 原因 :这是对
this
的直接调用,绕过了代理对象,因此事务拦截器不会生效。- 解决 :
- 重构代码,将事务方法移到另一个 Bean 中。
- 通过
ApplicationContext
或AopContext.currentProxy()
获取代理对象进行调用(不推荐,破坏了透明性)。3. 异常被捕获但未抛出
- 场景 :
@Transactional
方法内部catch
了异常,但没有重新抛出。- 原因 :Spring 默认只在抛出 未检查异常 (
RuntimeException
及其子类)或Error
时才回滚事务。如果异常被catch
并"吞掉",Spring 认为方法执行成功,会提交事务。- 解决 :
- 在
catch
块中重新抛出异常。- 使用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
手动标记回滚。4. 异常类型不匹配
- 场景 :抛出了
Exception
(检查异常),但未配置回滚。- 原因 :默认情况下,Spring 只对
RuntimeException
和Error
回滚。对于Exception
及其子类(如IOException
),默认是提交的。- 解决 :在
@Transactional(rollbackFor = Exception.class)
中指定需要回滚的异常类型。5. 数据库引擎不支持事务
- 场景:使用 MySQL 的 MyISAM 存储引擎。
- 原因:MyISAM 不支持事务,只有 InnoDB 支持。
- 解决:确保数据库表使用支持事务的存储引擎(如 InnoDB)。
6. 传播行为配置不当
- 场景 :外层方法非事务,内层方法
@Transactional(propagation = Propagation.NESTED)
或REQUIRES_NEW
,但外层捕获了异常。- 原因:某些传播行为对调用环境有要求,配置不当可能导致事务未按预期工作。
- 解决 :理解并正确使用
Propagation
各个值的含义。7. Bean 没有被 Spring 管理
- 场景 :通过
new
关键字创建的对象,或未被@Component
等注解标记的类。- 原因:Spring 无法为非容器管理的 Bean 创建代理,因此事务无效。
- 解决:确保类被 Spring 扫描并管理。
8. 代理失效(如
final
方法或类)
- 场景 :
@Transactional
方法是final
的,或者类是final
的。- 原因 :JDK 动态代理无法代理
final
方法,CGLIB 无法继承final
类,导致代理创建失败。- 解决 :避免对
final
方法或类使用@Transactional
。9. 未开启事务管理
- 场景 :没有配置
@EnableTransactionManagement
注解或相应的 XML 配置。- 原因:Spring 不知道要启用事务管理功能。
- 解决 :在配置类上添加
@EnableTransactionManagement
。
Spring的事务,使用this调用是否生效?
在 Spring 中,使用
this
调用一个被@Transactional
注解标记的方法,事务不会生效。这背后的根本原因在于 Spring AOP 的代理机制。
- 代理模式 :Spring 的事务管理是基于 AOP 实现的。当一个 Bean 被 Spring 容器管理时,如果它有
@Transactional
方法,Spring 会为这个 Bean 创建一个代理对象(Proxy)。- 代理拦截:真正的事务逻辑(开启、提交、回滚)是在这个代理对象中实现的。当你通过 Spring 容器获取 Bean 并调用其方法时,实际上是调用了代理对象的方法,代理会在目标方法执行前后添加事务控制。
this
调用绕过代理 :当你在同一个类中使用this.methodB()
来调用另一个方法时,这是对当前对象实例的直接调用,完全绕过了代理对象。JVM 直接执行了目标方法,Spring 的事务拦截器根本没有机会介入。
Spring 提供了那些事务传播行为
Spring 的
@Transactional
注解通过propagation
属性来定义事务的传播行为(Propagation Behavior),它决定了当一个事务性方法被另一个事务性方法调用时,事务应该如何进行。
播行为 当前有事务 当前无事务 REQUIRED
加入 创建新事务 SUPPORTS
加入 非事务执行 MANDATORY
加入 抛异常 REQUIRES_NEW
挂起,创建新事务 创建新事务 NOT_SUPPORTED
挂起,非事务执行 非事务执行 NEVER
抛异常 非事务执行 NESTED
创建嵌套事务 创建新事务 1.
REQUIRED
(默认)
- 含义:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
- 场景:最常用。适用于大多数业务场景,保证操作在事务中进行。
- 示例 :方法 A(有事务)调用方法 B(
REQUIRED
),B 会加入 A 的事务。2.
SUPPORTS
- 含义:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- 场景:用于一些查询操作,既能融入现有事务,也能独立运行。
- 示例 :方法 A(无事务)调用方法 B(
SUPPORTS
),B 以非事务方式执行。3.
MANDATORY
- 含义:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 场景:强制要求调用方必须提供事务上下文。
- 示例 :方法 A(无事务)调用方法 B(
MANDATORY
),会抛出IllegalTransactionStateException
。4.
REQUIRES_NEW
- 含义 :无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将其挂起。
- 场景:用于需要独立提交或回滚的操作,如记录日志、发送通知,即使外围事务回滚,这些操作也应成功。
- 示例 :方法 A(有事务)调用方法 B(
REQUIRES_NEW
),A 的事务被挂起,B 在新事务中执行。5.
NOT_SUPPORTED
- 含义:以非事务方式执行操作。如果当前存在事务,则将其挂起。
- 场景:用于一些不需要事务的操作,避免其受外围事务影响。
- 示例 :方法 A(有事务)调用方法 B(
NOT_SUPPORTED
),A 的事务被挂起,B 以非事务方式执行。6.
NEVER
- 含义:以非事务方式执行。如果当前存在事务,则抛出异常。
- 场景:强制要求该方法不能在任何事务中运行。
- 示例 :方法 A(有事务)调用方法 B(
NEVER
),会抛出异常。7.
NESTED
- 含义:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新事务。
- 区别:嵌套事务是外部事务的一部分,拥有自己的回滚点(savepoint)。嵌套事务回滚不会影响外部事务,但外部事务回滚会导致嵌套事务也回滚。
- 依赖:需要底层数据库支持保存点(Savepoints)。
- 示例 :方法 A(有事务)调用方法 B(
NESTED
),B 在 A 的事务内创建一个保存点,B 可以独立回滚到该点。
MVC分层介绍一下
MVC(Model-View-Controller)是一种经典的软件架构设计模式,主要用于将应用程序的业务逻辑、数据 和用户界面分离,从而提高代码的可维护性、可扩展性和可测试性。
- 视图(view): 为用户提供使用界面,与用户直接进行交互。
- 模型(model): 代表一个存取数据的对象或JAVA POJO (Plain Old Java Object,简单java对象)。它也可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。模型分为两类**,一类称为数据承载 Bean,一类称为业务处理Bean。**
所谓数据承载 Bean 是指实体类(如:User类),专门为用户承载业务数据的;
而业务处理 Bean 则是指Service 或 Dao 对象, 专门用于处理用户提交请求的。- 控制器(controller): 用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。它使视图与模型分离。
流程步骤:
- 用户通过View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等;
- 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Model 处理;
- 将处理结果再交给 Controller(控制器其实只是起到了承上启下的作用);
- 根据处理结果找到要作为向客户端发回的响应View 页面,页面经渲染后发送给客户端。
在 Spring MVC 框架中,MVC 模式得到了完美实现:
- Model :
@Service
,@Repository
, 实体类。- View :Thymeleaf、JSP、或
@RestController
返回的 JSON 数据。- Controller :
@Controller
或@RestController
注解的类。
为什么使用springboot
使用 Spring Boot 主要是为了简化 Spring 应用的初始搭建和开发过程,解决传统 Spring 框架"配置繁琐、启动慢、依赖管理复杂"等问题。
- 简化开发 :Spring Boot通过提供一系列的开箱即用的组件和自动配置**,简化了项目的配置和开发过程**,开发人员可以更专注于业务逻辑的实现,而不需要花费过多时间在繁琐的配置上。
- **快速启动:**Spring Boot提供了快速的应用程序启动方式,可通过内嵌的Tomcat、Jetty或Undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷。
- 自动化配置:Spring Boot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,使开发者更容易实现应用的最佳实践。
传统 Spring Spring Boot 配置繁琐(XML/Java Config) 约定优于配置,自动配置 依赖管理复杂,版本易冲突 起步依赖,统一管理版本 需外部应用服务器部署 内嵌服务器, java -jar
直接运行缺乏生产监控工具 Actuator 提供开箱即用的监控端点
使用 Spring Boot 的核心原因:
1. 约定优于配置(Convention over Configuration)
- 问题 :传统 Spring 项目需要大量的 XML 或 Java 配置(如
DispatcherServlet
、DataSource
、TransactionManager
等),配置复杂且容易出错。- Spring Boot 方案 :提供合理的默认配置。例如,添加了
spring-boot-starter-web
依赖后,Spring Boot 会自动配置好嵌入式 Tomcat、Spring MVC 等,无需手动配置。开发者只需在必要时覆盖默认值。2. 内嵌服务器(Embedded Server)
- 问题:传统 Web 应用需要将 WAR 包部署到外部应用服务器(如 Tomcat、Jetty)。
- Spring Boot 方案 :内置了 Tomcat、Jetty 或 Undertow 服务器。应用打包成 JAR 文件后,可以直接通过
java -jar
命令运行,无需外部服务器,部署极其简单。3. 起步依赖(Starter Dependencies)
- 问题:手动管理 Spring 及其生态组件(如 Web、Data JPA、Security)的版本兼容性非常麻烦。
- Spring Boot 方案 :提供了
spring-boot-starter-*
系列依赖。例如:
spring-boot-starter-web
:一键引入 Web 开发所需的所有库(Spring MVC、Tomcat、Jackson 等)。spring-boot-starter-data-jpa
:引入 JPA、Hibernate、数据源等。- 这些"起步依赖"本质上是管理了一组协调版本的依赖,极大简化了
pom.xml
文件。4. 自动配置(Auto-configuration)
- 原理:Spring Boot 在类路径下检查所需的库,根据存在的 jar 包和配置文件,自动配置相应的 Bean。
- 示例 :如果类路径中有
H2
数据库驱动和spring-jdbc
,但没有手动配置DataSource
,Spring Boot 会自动配置一个内存数据库的数据源。5. 生产就绪(Production-ready)功能
- Actuator :提供了一系列用于监控和管理应用的端点(如
/health
,/metrics
,/info
,/shutdown
),便于了解应用的运行状态。- 外部化配置 :支持从
application.properties
/application.yml
、环境变量、命令行参数等多种方式注入配置,方便不同环境(dev/test/prod)的切换。- 指标监控:与 Micrometer 集成,轻松实现应用性能监控。
6. 简化的开发体验
- 热部署(Devtools) :添加
spring-boot-devtools
后,代码修改后可自动重启应用,大幅提升开发效率。- 无代码生成:无需生成额外代码,基于注解和配置即可工作。
- 强大的 CLI 工具:可通过命令行快速创建和运行 Groovy 脚本。
什么是约定优于配置
"约定优于配置"(Convention over Configuration,简称 CoC)是一种软件设计范式,其核心思想是:通过提供一系列合理的默认约定,来减少开发人员需要做的配置决策,从而简化开发过程。
- 自动化配置 :Spring Boot 提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为。开发者无需显式地配置每个细节,大部分常用的配置都已经预设好了。例如,引入
spring-boot-starter-web
后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需手动编写XML。- 默认配置 :Spring Boot 为诸多方面提供大量默认配置,如连接数据库、设置 Web 服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。
例如,默认的日志配置可让应用程序快速输出日志信息,无需开发者额外繁琐配置日志级别、输出格式与位置等。- 约定的项目结构 :Spring Boot 提倡特定项目结构,通常主应用程序类(含 main 方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包。
如com.example.demo.controller
放控制器类,com.example.demo.service
放服务类等。此约定使团队成员更易理解项目结构与组织,新成员加入项目时能快速定位各功能代码位置,提升协作效率。
Springboot最常用的启动器(starter)?
spring-boot-starter-web
- 用途:构建 Web 应用,包括 RESTful API。
- 包含 :Spring MVC、内嵌 Tomcat、Jackson(JSON 处理)等。Web 开发必备。
spring-boot-starter-data-jpa
- 用途:简化数据库操作,使用 JPA/Hibernate 进行数据持久化。
- 包含 :Spring Data JPA、Hibernate、连接池等。数据库开发常用。
spring-boot-starter-data-redis
- 用途:集成 Redis,用于缓存、会话管理等。
- 包含 :Lettuce 或 Jedis 客户端、Spring Data Redis。缓存场景必备。
spring-boot-starter-security
- 用途:提供安全控制,如认证(Authentication)和授权(Authorization)。
- 包含 :Spring Security 框架。需要权限管理时使用。
spring-boot-starter-test
- 用途:提供测试支持。
- 包含 :JUnit、Mockito、Spring Test 等。单元测试和集成测试必备。
spring-boot-starter-actuator
- 用途:监控和管理生产环境中的应用。
- 包含 :健康检查
/health
、指标/metrics
等端点。生产运维常用。spring-boot-starter-thymeleaf
- 用途:服务端 HTML 模板渲染(传统 Web 页面)。
- 包含 :Thymeleaf 模板引擎。需要返回动态页面时使用。
Springboot怎么做到导入就可以直接使用的?
核心正是依赖于 起步依赖(Starter Dependencies) 、自动配置(Auto-configuration) 和 条件注解(Conditional Annotations) 这三大机制的协同工作。
- 起步依赖 :
spring-boot-starter-*
一键引入一整套功能相关的、版本兼容的依赖包。- 自动配置:根据你引入的依赖,自动帮你配置好相应的 Bean(如数据源、Web 组件)。
- 条件注解 :通过
@ConditionalOnClass
等注解,确保只有在需要时才进行自动配置,避免冲突。
SpringBoot 过滤器和拦截器说一下?
特性 过滤器 (Filter) 拦截器 (Interceptor) 技术规范 Servlet 规范(Java EE) Spring MVC 框架 作用范围 所有进入容器的请求(包括静态资源) 仅 Spring MVC 映射的请求(Controller) 依赖 不依赖 Spring,但可被 Spring 管理 依赖 Spring MVC 执行时机 doFilter()
前后preHandle
,postHandle
,afterCompletion
获取信息 ServletRequest
,ServletResponse
可获取 Handler
(目标方法)、ModelAndView
等 Spring MVC 对象配置方式 @Component
,@WebFilter
,FilterRegistrationBean
实现 WebMvcConfigurer.addInterceptors()
如何选择?
- 如果需要处理与 Spring 无关 的底层请求(如统一编码、跨域、过滤所有资源),使用 Filter。
- 如果需要处理业务相关的逻辑 ,且需要访问 Spring MVC 的上下文(如 Controller 方法、Model 数据),使用 Interceptor。
在实际项目中,两者经常结合使用,例如:用 Filter 处理跨域和编码,用 Interceptor 处理权限认证。
Spring Bean的加载流程
简单来说,Spring Bean的加载流程就几步:
- 读配置 :Spring先看看你用
@Component
、@Bean
这些注解或XML写了哪些类要创建。- 实例化:通过反射把类new出来,生成一个对象。
- 填属性 :给这个对象的成员变量注入值,比如用
@Autowired
把依赖的对象塞进去。- 初始化 :调用
@PostConstruct
或InitializingBean
的方法,做一些准备操作。- 放入容器:如果是单例(默认),就存到一个map里,下次直接用。
- 销毁 :容器关闭时,调用
@PreDestroy
释放资源。整个过程就是:创建 → 装配 → 初始化 → 使用 → 销毁。
Spring帮你全自动管理,不用手动new,也不用担心重复创建。
在 SpringTomcat、Jetty 或 Undertow 服务器各自的作用
在 Spring(尤其是 Spring Boot)中,Tomcat、Jetty 和 Undertow 的作用都一样:
作为内嵌的 Web 服务器,负责处理 HTTP 请求,让你的 Spring 应用能对外提供 Web 服务。
它们是 Spring 应用和外界通信的"门户"。
服务器 默认? 特点 适用场景 Tomcat ✅ 是(Spring Boot 默认) 稳定、功能全、社区大 大多数 Web 项目 Jetty ❌ 否 轻量、启动快、内存少 微服务、嵌入式、测试 Undertow ❌ 否 性能高、基于 NIO、内存占用低 高并发、性能敏感场景
监听网络请求
- 启动后,绑定端口(比如 8080),等待浏览器、APP 或其他服务发来的 HTTP 请求(如 GET /login)。
解析 HTTP 请求
- 把原始的网络数据(请求头、请求体、参数等)解析成 Java 能处理的对象(如
HttpServletRequest
)。调用你的代码
- 根据请求路径,找到你写的 Spring Controller、Servlet 等业务逻辑,把请求交给它们处理。
返回响应
- 把你返回的数据(比如 JSON、页面)包装成 HTTP 响应,发回给客户端。
管理线程和连接
- 多个用户同时访问时,它们会用线程池处理并发,保证性能。
spring的启动过程
1. 启动入口
SpringApplication.run(Application.class, args);
这是启动的起点,
Application
是主配置类(通常带有@SpringBootApplication
)。2. 创建 SpringApplication 对象
- 设置应用类型(Web 应用还是普通 Java 程序)
- 加载初始器、监听器等扩展组件
3. 运行 Spring 应用上下文(ApplicationContext)
这是最核心的部分:
✅ (1) 准备环境(Environment)
- 加载配置文件:
application.properties
或application.yml
- 处理命令行参数、系统属性等
✅ (2) 创建并刷新 IOC 容器
- 加载 Bean 定义 :扫描
@Component
、@Service
、@Controller
等注解,注册 Bean。- 实例化 Bean:创建对象(如 Controller、Service)。
- 依赖注入(DI) :通过
@Autowired
注入依赖。- 初始化 Bean :调用
@PostConstruct
、InitializingBean
等。✅ (3) 启动内嵌 Web 服务器
- 如果是 Web 项目(如 Spring Boot),会启动 Tomcat、Jetty 或 Undertow。
- 把 Spring 的 DispatcherServlet 注册进去,开始监听端口(如 8080)。
✅ (4) 执行 CommandLineRunner / ApplicationRunner
- 如果你有类实现了这些接口,会在启动完成后自动执行,适合做初始化任务。
4. 启动完成
- 打印日志:"Started Application in X seconds"
- 应用开始对外提供服务(接收 HTTP 请求