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 请求
相关推荐
Java中文社群3 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme3 小时前
java.nio 包详解
java·python·nio
零千叶3 小时前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝3 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908903 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠3 小时前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
Aevget4 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(二)
java·ide·java-ee·eclipse·myeclipse
黄昏晓x4 小时前
C++----多态
java·jvm·c++
Brookty4 小时前
【算法】前缀和
java·学习·算法·前缀和·动态规划