Spring面试

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() 方法创建代理对象,所有方法调用都会被 InvocationHandlerinvoke() 方法拦截。
  • 优点:是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容器)就会帮你准备好食材、切好、炒好,你直接吃就行,不用自己去洗菜切菜炒菜

具体流程

  1. 配置元信息读取

    • Spring 容器启动时,会读取配置元信息。这些信息可以来自 XML 文件、Java 注解(如 @Component, @Service, @Repository, @Controller)或 Java 配置类(@Configuration)。
    • 这些配置告诉容器:"有哪些 Bean 需要管理,它们的依赖关系是什么"。
  2. Bean 定义注册

    • 容器将读取到的配置信息解析并封装成 BeanDefinition 对象。
    • BeanDefinition 就像是一个"蓝图",它包含了 Bean 的类名、作用域(singleton/prototype)、生命周期回调方法、属性值、依赖的其他 Bean 名称等所有创建和管理 Bean 所需的信息。
    • 这些 BeanDefinition 被注册到一个 BeanDefinitionRegistry(本质上是一个 Map)中,以 Bean 的名称为 key。
  3. Bean 实例化

    • 当需要获取一个 Bean 时(例如通过 getBean() 或依赖注入),容器会根据 BeanDefinition 中的类名,使用 Java 反射Class.forName()newInstance(),或构造函数反射)来创建该类的实例。
    • 对于单例(Singleton)Bean,这个实例化过程通常在容器启动时就完成了(预实例化)。
  4. 依赖注入(DI)

    • 实例化后,容器会检查该 Bean 的 BeanDefinition,看它依赖了哪些其他 Bean。
    • 容器会从自己的"工厂"里查找(或创建)这些依赖的 Bean。
    • 然后通过 反射 调用 setter 方法(@Autowired on setter/field)或直接设置字段值(@Autowired on field),或者通过构造函数注入(@Autowired on constructor),将依赖的对象"注入"到当前 Bean 中。
  5. Bean 生命周期管理

    • 在 Bean 的创建过程中,容器会执行一系列生命周期回调:
      • 初始化前 :调用 BeanPostProcessorpostProcessBeforeInitialization 方法。
      • 初始化 :调用 @PostConstruct 注解的方法,或 InitializingBeanafterPropertiesSet() 方法,或在 BeanDefinition 中指定的 init-method
      • 初始化后 :调用 BeanPostProcessorpostProcessAfterInitialization 方法。AOP 代理通常在这里生成。
    • 对于单例 Bean,创建好的实例会被放入单例缓存池(一级缓存)中,后续请求直接返回该实例。
    • 容器关闭时,会调用 @PreDestroyDisposableBeandestroy()destroy-method 来清理资源。

总结来说,Spring IoC 容器就像一个超级智能的"对象工厂":

  • 读取蓝图BeanDefinition
  • 按图索骥(反射创建实例)
  • 组装零件(依赖注入)
  • 质量检查(生命周期回调)
  • 入库待取(放入缓存)

它通过反射 技术实现了对象的动态创建和属性的动态设置,通过工厂模式统一管理对象的生命周期,最终实现了控制反转,让开发者从繁琐的对象管理中解放出来。

Spring AOP 实现机制

Spring AOP 的实现机制主要依赖于动态代理技术,在运行时为需要增强的目标对象创建一个代理对象,从而在不修改原始代码的情况下,将横切逻辑(切面)织入到业务流程中。

  1. 识别切面和目标

    • Spring 容器启动时,会扫描所有被 @Aspect 注解标记的类,识别出切面(Aspect)。
    • 同时,容器会根据切面中定义的切入点 (Pointcut)表达式,找到所有匹配的目标对象(Target Object)。
  2. 创建代理对象

    • 对于每一个需要被 AOP 增强的目标对象,Spring 会创建一个代理对象(Proxy)。这个代理对象会替代原始的目标对象被注入到其他 Bean 中。
    • Spring 选择代理策略遵循以下规则:
      • JDK 动态代理 :如果目标对象实现了至少一个接口 ,Spring 会使用 JDK 的 java.lang.reflect.Proxy 机制,创建一个实现了与目标对象相同接口的代理类。代理类会持有目标对象的引用,并在调用接口方法时进行拦截。
      • CGLIB 动态代理 :如果目标对象没有实现任何接口 ,Spring 会使用 CGLIB 库,通过继承 目标类的方式创建一个子类作为代理。这个子类会重写目标类的所有非 final 方法,并在这些方法中插入拦截逻辑。
  3. 织入通知

    • 代理对象中包含了切面的通知(Advice)逻辑。
    • 当客户端代码调用目标方法时,实际上是调用了代理对象的方法。
    • 代理对象会根据配置的通知类型(@Before, @After, @Around 等),在调用真正的目标方法之前之后环绕执行切面中定义的额外逻辑。
    • 例如,一个 @Around 通知可以用来开启事务,然后调用目标方法,最后根据结果提交或回滚事务。
  4. 运行时织入

    • 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)

  • 定义 :在运行时由程序动态生成代理类的字节码,并创建代理对象。
  • 特点
    • 运行时生成:代理类不是提前写好的,而是在程序运行过程中,根据需要动态创建的。
    • 一对多 :一个通用的代理逻辑(如 InvocationHandlerMethodInterceptor)可以代理多个不同的目标类,只要它们符合代理的条件(如实现接口或非final类)。
    • 高灵活性:可以灵活地为任何符合条件的类添加通用功能(如日志、事务),极大地减少了代码重复。
    • 依赖技术:需要依赖反射(JDK)或字节码生成技术(CGLIB)。
  • 例子:就像一个智能安保系统,它可以动态地为公司里任何员工(目标类)生成一个专属的虚拟保镖(代理对象),并且这个保镖的行为规则(如检查门禁、记录日志)是统一配置的,不需要为每个员工单独定制一套安保方案。

什么是动态代理

动态代理是一种在程序运行时动态地创建代理对象的技术,而不是在编译时就写好代理类。这个代理对象可以用来控制对真实目标对象的访问,并在调用目标对象方法的前后添加额外的逻辑(比如日志、事务、权限检查等)。

核心特点

  1. 运行时生成:代理类是在程序运行过程中,根据需要动态生成的,而不是在编码时手动编写的。
  2. 灵活性高:同一个代理逻辑可以适用于多个不同的目标类,只要它们符合一定的条件(如实现接口或非final类)。
  3. 解耦:业务逻辑和通用功能(如日志、事务)分离,提高了代码的复用性和可维护性。

在 Java 中,动态代理主要有两种实现:

  1. JDK 动态代理

    • 原理:基于 Java 的反射机制,要求目标对象必须实现一个或多个接口。
    • 实现 :通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口来创建代理。
    • 过程 :代理对象会实现与目标对象相同的接口。当调用代理对象的方法时,所有调用都会被转发到 InvocationHandlerinvoke() 方法中,在这里可以添加通用逻辑,再通过反射调用目标对象的真正方法。
    • 优点:Java 原生支持,无需额外依赖。
    • 缺点:只能代理实现了接口的类。
  2. CGLIB 动态代理

    • 原理:基于字节码生成库(ASM),通过继承目标类来创建代理。
    • 实现 :使用 net.sf.cglib.proxy.Enhancer 类和 MethodInterceptor 接口。
    • 过程 :CGLIB 会生成一个目标类的子类作为代理类,并重写其中的非 final 方法。在重写的方法中,会先调用拦截器的 intercept() 方法来执行通用逻辑,然后再调用父类(即目标对象)的方法。
    • 优点:可以代理没有实现接口的类。
    • 缺点 :需要引入 CGLIB 库,无法代理 final 类和 final 方法。

什么是反射?有哪些使用场景?

反射(Reflection) 是 Java 的一种强大特性,它允许程序在运行时动态地获取类的信息(如类名、属性、方法、构造器等),并能操作这些信息,比如创建对象、调用方法、访问和修改字段值,即使在编写代码时并不知道这些类的具体细节。

主要功能

使用 Java 的 java.lang.reflect 包,反射可以做到:

  1. 获取类信息 :通过类名获取 Class 对象,进而得到类的完整信息。
  2. 创建实例 :无需使用 new,即可通过 Class.newInstance() 或构造函数反射来创建对象。
  3. 调用方法 :通过 Method 对象,动态调用对象的方法,包括私有方法(通过 setAccessible(true))。
  4. 访问字段 :通过 Field 对象,读取或修改对象的字段值,包括私有字段。
  5. 获取注解:检查类、方法、字段上的注解信息。

常见的使用场景

比如开发框架时Spring 用它来注入依赖,

MyBatis用它映射 SQL 结果到对象;

写工具类时,JSON库用它把对象转成字符串;

甚至实现插件系统,让程序能加载外部类并实例化。

不过反射虽好,用多了会影响性能,毕竟"透视"总比直接操作费劲,所以一般只在需要动态灵活性的场景用它**。**

  1. 框架开发

    • Spring IoC/DI:Spring 容器就是靠反射来创建 Bean 实例,并通过 setter 或字段注入依赖的。没有反射,就无法实现控制反转。
    • MyBatis / Hibernate:ORM 框架使用反射将数据库查询结果映射到 Java 对象(POJO)的字段上。它会根据实体类的属性名找到对应的表列,并通过 setter 方法或直接设置字段来填充数据。
    • Spring MVC :处理请求时,通过反射调用控制器(Controller)中被 @RequestMapping 注解标记的方法。
  2. 通用工具库

    • JSON 序列化/反序列化(如 Jackson, Gson):将 Java 对象转换成 JSON 字符串,或者将 JSON 解析成 Java 对象时,需要遍历对象的所有字段,这都依赖于反射。
    • 对象拷贝工具 :像 BeanUtils 这样的工具,可以在不知道具体类的情况下,复制两个对象之间的同名属性,背后就是反射在工作。
  3. 插件化系统

    • 程序可以在运行时加载外部的 .class 文件或 JAR 包,通过反射获取类信息并实例化对象,从而实现热插拔和功能扩展。
  4. 单元测试和 Mock 框架

    • 像 JUnit 和 Mockito 这样的测试框架,经常需要访问私有方法或字段来进行测试,或者动态生成代理类来模拟(Mock)对象的行为,这都需要反射的支持。
  5. 注解处理

    • 很多基于注解的功能(如 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 三级缓存机制设计的初衷。流程如下:
    1. 创建 A,实例化后(A 已 new 出来),将其 ObjectFactory 放入三级缓存。
    2. 注入 A 的属性时发现需要 B,于是暂停 A 的创建,转而去创建 B。
    3. 创建 B,实例化后,将其 ObjectFactory 放入三级缓存。
    4. 注入 B 的属性时发现需要 A。此时从三级缓存获取 A 的 ObjectFactory,调用 getObject() 提前暴露 A 的早期引用(半成品),并放入二级缓存。
    5. 将 A 的早期引用注入到 B 中,B 创建完成,放入一级缓存。
    6. 回到 A,将完整的 B 注入,A 创建完成,放入一级缓存。
  • 关键:依赖注入发生在实例化之后,因此可以提前暴露"半成品"来打破循环。

spring是如何解决循环依赖的?

Spring 主要通过 三级缓存 机制来解决单例 Bean 的循环依赖问题,但这种解决方式有其特定的前提和限制。

Spring 的 IoC 容器为单例 Bean 的创建过程维护了三个 Map(缓存),它们共同协作来打破循环依赖的死锁:

  1. 一级缓存 singletonObjects

    • 存放完全初始化好的单例 Bean。
    • 这是最终的"成品仓库",当其他 Bean 需要依赖时,会优先从这里获取。
    • 数据结构:ConcurrentHashMap<String, Object>
  2. 二级缓存 earlySingletonObjects

    • 存放已经实例化但尚未完成属性填充和初始化的"早期引用"(半成品 Bean)。
    • 当一个 Bean 被提前暴露给其他 Bean 依赖时,它会暂时存放在这里。
    • 数据结构:ConcurrentHashMap<String, Object>
  3. 三级缓存 singletonFactories

    • 存放一个对象工厂ObjectFactory),这个工厂封装了创建早期引用的逻辑。
    • 当一个 Bean 开始创建时,Spring 会先将一个能创建该 Bean 早期引用的工厂放入这里。
    • 数据结构:ConcurrentHashMap<String, ObjectFactory<?>>

工作流程(以 A 依赖 B,B 依赖 A 为例)

  1. 创建 A

    • Spring 开始创建 Bean A。
    • 先调用 A 的构造函数,完成实例化(A 已经被 new 出来了,但属性还未注入)。
    • 将一个能获取 A 的早期引用的工厂(ObjectFactory)放入三级缓存
    • 开始为 A 注入属性,发现需要依赖 Bean B。
  2. 创建 B

    • Spring 发现需要创建 Bean B,于是暂停 A 的创建流程,转而去创建 B。
    • 调用 B 的构造函数,完成实例化。
    • 将能获取 B 的早期引用的工厂放入三级缓存
    • 开始为 B 注入属性,发现需要依赖 Bean A。
  3. 解决 B 对 A 的依赖(打破循环)

    • B 需要 A,Spring 先从一级缓存找,没有(A 还没创建完)。
    • 再从二级缓存找,也没有。
    • 最后从三级缓存找到 A 对应的 ObjectFactory
    • 调用这个工厂的 getObject() 方法,提前暴露 A 的早期引用(此时的 A 是个"半成品",只有实例,属性还没注入)。
    • 将这个早期引用从工厂中取出,并放入二级缓存(避免重复创建)。
    • 将 A 的早期引用注入到 B 中。
  4. 完成 B 的创建

    • B 成功注入了 A(虽然是个早期引用),继续完成 B 的其他初始化工作。
    • B 创建完成后,从三级缓存移除其工厂,将其放入一级缓存
  5. 完成 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 容器的核心。BeanFactoryApplicationContext 都是典型的工厂。
  • 作用 :它们负责创建、配置和管理 Bean 对象。开发者不需要直接使用 new 来创建对象,而是通过工厂(容器)来获取,实现了对象创建的解耦。

2. 单例模式 (Singleton Pattern)

  • 应用 :Spring 中 Bean 的默认作用域就是 singleton
  • 作用 :容器确保一个 Bean 定义在整个应用上下文中只对应一个实例。这通过容器内部的缓存(如 singletonObjects)来实现,保证了资源的高效利用。

3. 代理模式 (Proxy Pattern)

  • 应用:Spring AOP 的基石。无论是 JDK 动态代理还是 CGLIB 代理,都属于代理模式。
  • 作用:为真实的目标对象创建一个代理对象,代理对象可以控制对目标对象的访问,并在调用前后添加额外逻辑(如事务、日志),而对客户端透明。

4. 模板方法模式 (Template Method Pattern)

  • 应用JdbcTemplateRestTemplateJmsTemplate 等各种 Template 类。
  • 作用 :这些类定义了操作的骨架(如获取连接、执行SQL、处理异常、关闭连接),将变化的部分(如 SQL 语句、结果集映射)封装成回调方法(如 RowMapper),由用户实现。这极大地简化了重复性代码。

5. 适配器模式 (Adapter Pattern)

  • 应用 :Spring MVC 中的 HandlerAdapter
  • 作用 :不同的控制器(Controller)可以有不同的实现方式(如实现 Controller 接口、使用 @RequestMapping 注解)。HandlerAdapter 作为适配器,能够统一调用这些不同类型的处理器,屏蔽了它们的差异。

6. 观察者模式 (Observer Pattern)

  • 应用 :Spring 的事件驱动模型(ApplicationEventApplicationListener)。
  • 作用 :当容器发布一个事件(如 ContextRefreshedEvent)时,所有监听该事件的监听器都会收到通知并执行相应逻辑,实现了组件间的松耦合通信。

7. 装饰器模式 (Decorator Pattern)

  • 应用BeanWrapperPropertyEditor 的设计中有所体现。
  • 作用:可以在不改变原始对象的情况下,动态地为对象添加新的职责或功能,比如类型转换、属性访问等。

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 中。
    • 通过 ApplicationContextAopContext.currentProxy() 获取代理对象进行调用(不推荐,破坏了透明性)。

3. 异常被捕获但未抛出

  • 场景@Transactional 方法内部 catch 了异常,但没有重新抛出。
  • 原因 :Spring 默认只在抛出 未检查异常RuntimeException 及其子类)或 Error 时才回滚事务。如果异常被 catch 并"吞掉",Spring 认为方法执行成功,会提交事务。
  • 解决
    • catch 块中重新抛出异常。
    • 使用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 手动标记回滚。

4. 异常类型不匹配

  • 场景 :抛出了 Exception(检查异常),但未配置回滚。
  • 原因 :默认情况下,Spring 只对 RuntimeExceptionError 回滚。对于 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 的代理机制

  1. 代理模式 :Spring 的事务管理是基于 AOP 实现的。当一个 Bean 被 Spring 容器管理时,如果它有 @Transactional 方法,Spring 会为这个 Bean 创建一个代理对象(Proxy)。
  2. 代理拦截:真正的事务逻辑(开启、提交、回滚)是在这个代理对象中实现的。当你通过 Spring 容器获取 Bean 并调用其方法时,实际上是调用了代理对象的方法,代理会在目标方法执行前后添加事务控制。
  3. 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 的计算结果向用户提供相应响应。它使视图与模型分离。

流程步骤:

  1. 用户通过View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等;
  2. 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Model 处理;
  3. 将处理结果再交给 Controller(控制器其实只是起到了承上启下的作用);
  4. 根据处理结果找到要作为向客户端发回的响应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 配置(如 DispatcherServletDataSourceTransactionManager 等),配置复杂且容易出错。
  • 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)?

  1. spring-boot-starter-web

    • 用途:构建 Web 应用,包括 RESTful API。
    • 包含 :Spring MVC、内嵌 Tomcat、Jackson(JSON 处理)等。Web 开发必备
  2. spring-boot-starter-data-jpa

    • 用途:简化数据库操作,使用 JPA/Hibernate 进行数据持久化。
    • 包含 :Spring Data JPA、Hibernate、连接池等。数据库开发常用
  3. spring-boot-starter-data-redis

    • 用途:集成 Redis,用于缓存、会话管理等。
    • 包含 :Lettuce 或 Jedis 客户端、Spring Data Redis。缓存场景必备
  4. spring-boot-starter-security

    • 用途:提供安全控制,如认证(Authentication)和授权(Authorization)。
    • 包含 :Spring Security 框架。需要权限管理时使用
  5. spring-boot-starter-test

    • 用途:提供测试支持。
    • 包含 :JUnit、Mockito、Spring Test 等。单元测试和集成测试必备
  6. spring-boot-starter-actuator

    • 用途:监控和管理生产环境中的应用。
    • 包含 :健康检查 /health、指标 /metrics 等端点。生产运维常用
  7. spring-boot-starter-thymeleaf

    • 用途:服务端 HTML 模板渲染(传统 Web 页面)。
    • 包含 :Thymeleaf 模板引擎。需要返回动态页面时使用

Springboot怎么做到导入就可以直接使用的?

核心正是依赖于 起步依赖(Starter Dependencies)自动配置(Auto-configuration)条件注解(Conditional Annotations) 这三大机制的协同工作。

  1. 起步依赖spring-boot-starter-* 一键引入一整套功能相关的、版本兼容的依赖包。
  2. 自动配置:根据你引入的依赖,自动帮你配置好相应的 Bean(如数据源、Web 组件)。
  3. 条件注解 :通过 @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的加载流程就几步:

  1. 读配置 :Spring先看看你用@Component@Bean这些注解或XML写了哪些类要创建。
  2. 实例化:通过反射把类new出来,生成一个对象。
  3. 填属性 :给这个对象的成员变量注入值,比如用@Autowired把依赖的对象塞进去。
  4. 初始化 :调用@PostConstructInitializingBean的方法,做一些准备操作。
  5. 放入容器:如果是单例(默认),就存到一个map里,下次直接用。
  6. 销毁 :容器关闭时,调用@PreDestroy释放资源。

整个过程就是:创建 → 装配 → 初始化 → 使用 → 销毁

Spring帮你全自动管理,不用手动new,也不用担心重复创建。

在 SpringTomcat、Jetty 或 Undertow 服务器各自的作用

在 Spring(尤其是 Spring Boot)中,Tomcat、Jetty 和 Undertow 的作用都一样

作为内嵌的 Web 服务器,负责处理 HTTP 请求,让你的 Spring 应用能对外提供 Web 服务。

它们是 Spring 应用和外界通信的"门户"。

服务器 默认? 特点 适用场景
Tomcat ✅ 是(Spring Boot 默认) 稳定、功能全、社区大 大多数 Web 项目
Jetty ❌ 否 轻量、启动快、内存少 微服务、嵌入式、测试
Undertow ❌ 否 性能高、基于 NIO、内存占用低 高并发、性能敏感场景
  1. 监听网络请求

    • 启动后,绑定端口(比如 8080),等待浏览器、APP 或其他服务发来的 HTTP 请求(如 GET /login)。
  2. 解析 HTTP 请求

    • 把原始的网络数据(请求头、请求体、参数等)解析成 Java 能处理的对象(如 HttpServletRequest)。
  3. 调用你的代码

    • 根据请求路径,找到你写的 Spring Controller、Servlet 等业务逻辑,把请求交给它们处理。
  4. 返回响应

    • 把你返回的数据(比如 JSON、页面)包装成 HTTP 响应,发回给客户端。
  5. 管理线程和连接

    • 多个用户同时访问时,它们会用线程池处理并发,保证性能。

spring的启动过程

1. 启动入口

复制代码
SpringApplication.run(Application.class, args);

这是启动的起点,Application 是主配置类(通常带有 @SpringBootApplication)。

2. 创建 SpringApplication 对象

  • 设置应用类型(Web 应用还是普通 Java 程序)
  • 加载初始器、监听器等扩展组件

3. 运行 Spring 应用上下文(ApplicationContext)

这是最核心的部分:

✅ (1) 准备环境(Environment)
  • 加载配置文件:application.propertiesapplication.yml
  • 处理命令行参数、系统属性等
✅ (2) 创建并刷新 IOC 容器
  • 加载 Bean 定义 :扫描 @Component@Service@Controller 等注解,注册 Bean。
  • 实例化 Bean:创建对象(如 Controller、Service)。
  • 依赖注入(DI) :通过 @Autowired 注入依赖。
  • 初始化 Bean :调用 @PostConstructInitializingBean 等。
✅ (3) 启动内嵌 Web 服务器
  • 如果是 Web 项目(如 Spring Boot),会启动 Tomcat、Jetty 或 Undertow。
  • 把 Spring 的 DispatcherServlet 注册进去,开始监听端口(如 8080)。
✅ (4) 执行 CommandLineRunner / ApplicationRunner
  • 如果你有类实现了这些接口,会在启动完成后自动执行,适合做初始化任务。

4. 启动完成

  • 打印日志:"Started Application in X seconds"
  • 应用开始对外提供服务(接收 HTTP 请求
相关推荐
暹罗软件开发11 小时前
快速搭建分布式链路追踪系统:SkyWalking全攻略
java·skywalking
.格子衫.11 小时前
Maven中的配置
java·maven
拉不动的猪11 小时前
# 关于初学者对于JS异步编程十大误区
前端·javascript·面试
L.EscaRC11 小时前
Spring Boot 自定义组件深度解析
java·spring boot·后端
pengzhuofan11 小时前
IntelliJ IDEA 常用快捷键
java·ide·intellij-idea
ANGLAL11 小时前
17.MyBatis动态SQL语法整理
java·sql·mybatis
SheepHappy12 小时前
MyBatis-Plus 源码阅读(二)代码生成器原理深度剖析
java·源码阅读
雨白12 小时前
重识 Java IO、NIO 与 OkIO
android·java
light_in_hand12 小时前
内存区域划分——垃圾回收
java·jvm·算法