spring重点详解

IOC与BEAN

ioc由来

在 Spring 框架诞生之前,传统 Java 企业级开发中,开发者创建对象、管理组件依赖全靠手动 new 实例化,类与类之间的依赖关系直接硬编码在业务代码里。

这种开发方式带来了严重问题:组件耦合度极高,修改、替换底层类需要改动多处业务代码;对象的创建、销毁、单例管理全由开发者手动控制,缺乏统一规范;同时依赖固化,难以做单元测试、模块复用,项目一旦规模变大,依赖关系错综复杂,维护和扩展成本极高。

控制反转(IoC)并不是 Spring 首创 ,它是一种很早就出现的软件设计思想,核心理念是把对象的创建权限、依赖管理权限,从业务代码中剥离出来,交给第三方容器统一管控

Spring 框架诞生后,为了彻底解决传统开发高耦合、难维护、难扩展的痛点,将 IoC 思想进行落地实现,自研了IOC 容器 。它把所有业务对象统一交由容器管理,由容器负责 Bean 的实例化、生命周期管理、依赖自动装配,将开发者主动主动依赖对象,转变为容器主动推送依赖,实现了控制权的反转。 自此,IOC 容器成为 Spring 最核心的基础,也奠定了 Spring 解耦、轻量化、易整合的框架特性。 依赖注入:IoC 是容器帮你管对象,依赖自动装配是容器帮你自动给对象配齐它需要的所有依赖。

ioc中bean的生命周期

Spring 启动时 Bean 的完整生命周期

Spring Bean 的生命周期是容器启动 → 实例化 → 属性赋值 → 初始化 → 业务使用 → 容器关闭 → 销毁的完整过程,也是面试和开发的核心知识点。

  1. Bean 实例化
  2. 属性填充 / 依赖注入
  3. Bean 后置处理器 前置处理
  4. Bean 初始化
  5. Bean 后置处理器 后置处理
  6. Bean 就绪,放入单例池对外服务
  7. 容器关闭,Bean 销毁

1. Bean 实例化

动作 Spring 根据 Bean 定义,通过反射构造器 new 出 Bean 原始对象。

能做什么

  • 自定义对象创建方式:工厂 Bean、静态工厂方法、实例工厂方法;
  • 此时只是空对象,属性还没赋值、依赖还没注入。

2. 属性填充 & 依赖注入

动作 自动装配 @Autowired、构造器、setter,把依赖 Bean、配置属性赋值到当前对象。

能做什么

  • 自动注入其他组件、注入配置文件参数;
  • 完成类与类之间依赖绑定,彻底解耦,不用手动 new。

3. BeanPostProcessor 初始化前置

动作 执行 postProcessBeforeInitialization

能做什么

  • 修改原始 Bean、做前置校验、包装对象
  • Spring AOP 动态代理就是在这个阶段生成代理对象

4. Bean 初始化(三种方式,优先级固定)

优先级:@PostConstruct 注解 > InitializingBean#afterPropertiesSet 接口 > xml init-method

动作依赖注入完成后,执行自定义初始化逻辑。

能做什么

  • 初始化资源:创建线程池、初始化缓存、加载字典数据;
  • 校验属性参数、建立数据库 / Redis 连接;
  • 做项目启动预热、初始化配置。

5. BeanPostProcessor 初始化后置

动作 执行 postProcessAfterInitialization

能做什么

  • 再次包装、替换 Bean 实例;
  • 对代理对象做最终加工、缓存代理 Bean。

6. Bean 就绪,存入单例池

动作单例 Bean 放入 Spring 单例池,常驻容器。

能做什么

  • 正式对外提供业务服务;
  • 接收请求、执行业务逻辑,整个项目运行期间可随时使用。

多例 Bean 不会放入单例池,每次获取都新建对象。


7. Bean 销毁(容器关闭时触发)

优先级:@PreDestroy 注解 > DisposableBean#destroy 接口 > xml destroy-method

动作Spring 容器关闭、销毁上下文时执行。

能做什么

  • 释放资源:关闭数据库 / Redis 连接、销毁线程池;
  • 清理本地缓存、保存临时数据、释放文件句柄;

注意:多例 Bean Spring 不管理销毁,不会执行销毁方法

bean单例池

Spring 单例池也叫一级缓存、单例 Bean 缓存池,底层本质是:

java

运行

javascript 复制代码
ConcurrentHashMap<String, Object> singletonObjects
  • Key:Bean 的名称
  • Value已经完全实例化、属性注入、初始化完成、可直接使用的完整单例 Bean

Spring 默认所有 Bean 都是 singleton 单例作用域 ,容器启动时创建好,放入单例池缓存起来

单例池 核心五大作用

1. 保证容器级全局单例

整个 Spring IoC 容器中,同一个 Bean 只会存在一个实例 。后续无论多少次 getBean()@Autowired 依赖注入,都是从单例池拿同一个对象,不会重复 new。

注意:Spring 单例是容器级单例,不是 JVM 全局单例;多个 Spring 容器之间互不影响。

2. 节省资源、提升启动与运行性能

对象创建、反射实例化、依赖注入、初始化方法执行都是耗时操作

  • 只在容器启动时创建一次,放入单例池
  • 后续直接复用,避免频繁反射创建对象
  • 减少内存占用、减少 GC 压力、大幅提升系统运行效率
3. 统一管控 Bean 完整生命周期

只有放入单例池的单例 Bean,Spring 才会全程托管:

  • 实例化 → 属性注入 → Aware 接口 → 初始化前后置处理器
  • 执行 @PostConstruct、InitializingBean
  • AOP 代理生成
  • 容器关闭时执行 @PreDestroy、销毁方法

原型 Bean 不进单例池 ,Spring 只负责创建,不管理销毁

4. 实现依赖注入直接复用

项目中所有 @Autowired、构造器注入、Setter 注入,底层都是直接从单例池取出现成 Bean 进行赋值,不用临时创建。

5. 解决 Spring 循环依赖的核心(重中之重)

Spring 解决单例 Bean 循环依赖三级缓存 ,而单例池就是一级缓存

  • 一级缓存(singletonObjects) :单例池,存放完全就绪、初始化完成的 Bean
  • 二级缓存:存放已实例化但未填充属性的半成品 Bean
  • 三级缓存:存放 Bean 工厂 ObjectFactory,用于延迟创建、兼顾 AOP 代理

循环依赖最终完好的 Bean 都会存入单例池,供其他 Bean 直接引用。

循环依赖

Spring 通过 三级缓存提前暴露实例化后但未初始化的对象引用 ,打破了循环创建的僵局,但前提是 不能通过构造器循环依赖

AOP详解&&JVM与AOP关联

为什么引入AOP

假设你每个业务方法都要做这些通用操作:

  • 接口日志入参 / 出参打印
  • 方法耗时统计
  • 事务开启 / 提交 / 回滚
  • 权限校验
  • 全局异常捕获
  • 接口限流、埋点统计

传统写法:把这些通用代码硬写在每一个业务方法里

  1. 大量重复代码,到处复制粘贴
  2. 业务代码和非业务代码强耦合,业务逻辑被杂七杂八的代码淹没
  3. 要改日志格式、改事务规则、加新校验,所有业务方法都要改,维护爆炸
  4. 违反单一职责、开闭原则

OOP 面向对象只能纵向通过继承、复用代码 ,但跨多个无关类、无关方法的通用逻辑,OOP 根本搞不定。

实现原理

Spring AOP 提供了两种动态代理实现,根据目标类的特性自动选择,这是 AOP 的底层基石:

1. JDK 动态代理(Spring 默认)

  • 适用场景 :目标类实现了业务接口
  • 实现原理 :代理类和目标类实现同一个接口,重写接口方法,在方法中插入切面逻辑,再调用目标对象的原方法。
  • 底层依赖 :JDK 原生的 java.lang.reflect.Proxy + InvocationHandler
  • 优点:JDK 原生,无需额外依赖
  • 缺点必须基于接口,无法代理没有实现接口的类

2. CGLIB 动态代理(Code Generation Library)

  • 适用场景 :目标类没有实现任何接口
  • 实现原理 :代理类继承目标类,重写目标方法,在方法中插入切面逻辑。
  • 底层依赖:ASM 字节码操作框架(直接生成子类字节码)
  • 优点:无需接口,直接代理普通类
  • 缺点 :无法代理 final 类 / final 方法(无法继承重写)

Spring 代理选择规则

  1. 目标类实现了接口 → 自动用 JDK 动态代理
  2. 目标类未实现接口 → 自动用 CGLIB 动态代理
  3. 可强制全局使用 CGLIB:配置 proxy-target-class=true

反射与jdk代理

表格

对比维度 反射 Reflection JDK 动态代理
本质 Java 基础元数据操作工具 基于反射封装的代理增强机制
是否生成新类 ❌ 不生成,只操作已有类 ✅ 运行时动态生成全新代理类
核心能力 读类信息、动态创建对象、主动调用方法 拦截方法调用、前后增强逻辑
方法拦截 ❌ 无拦截能力,只能主动调用 ✅ 天生就是为拦截而生
依赖接口 不需要接口,任意类都能反射 必须基于接口才能代理
独立性 可单独使用,和代理无关 底层强依赖反射,不能脱离反射
典型用途 ORM 框架、注解解析、AOP 切点解析 Spring AOP 接口类代理、事务拦截
调用控制权 代码主动发起调用 外部调用先走代理,再转发原方法

代理运行步骤

  1. @Autowired 注入拿到的是 代理对象,不是原生目标对象
  2. 你调用:userService.save()
  3. 实际执行的是:代理类里的 save () 方法
  4. 先跑代理类里面的 AOP 通用逻辑(前置、日志、权限、事务)
  5. 代理再主动去调用 目标类原生的 save () 业务逻辑

1. JDK 代理

代理类 和 目标类 实现同一个接口 → 代理类里有完全一模一样的方法→ 一调用就进代理的方法,先执行切面逻辑,再反射调目标类方法

2. CGLIB 代理

代理类 是 目标类的子类 → 代理类重写了父类(目标类)一模一样的方法 → 一调用就进重写后的代理方法,先走切面逻辑,再 super 调用父类原方法

注解分类

  1. 源码级注解(Source)

    • 实现 :仅存于源代码中,编译后就被丢弃,不会写入 .class 文件。
    • 用途:仅供编译器或代码检查工具使用,不参与运行时或字节码处理。
    • 例子@Override@SuppressWarnings
    • 本质:纯粹的"标记",编译后消失。
  2. 编译时注解(Class)

    • 实现 :保留在 .class 文件中,但 JVM 加载类时不会读入到内存,运行时无法通过反射获取。
    • 用途:用于编译期的注解处理(如 Lombok、MapStruct 生成代码),或者字节码工具在构建阶段扫描并修改字节码。
    • 例子 :Lombok的 @Getter(生成 getter 后注解本身可丢弃)、某些框架的编译时路由表。
    • 本质:写给编译器或字节码分析工具看,运行时不再需要。
  3. 运行时注解(Runtime)

    • 实现 :保留到 .class 文件,并且在 JVM 加载类时也会保留到内存,可通过反射读取。
    • 用途:依赖 JVM 动态映射,框架在运行时获取注解信息并做出行为(如依赖注入、AOP、测试框架)。
    • 例子 :Spring @Autowired、JPA @Entity、JUnit @Test
    • 本质:需要 JVM 配合,运行时动态配合。

Spring 注解体系很庞大,我将通过一个功能分类总览,为你梳理最常用的一批注解及其核心用法。

功能分类 常用注解 核心作用
🚀 Spring Boot 核心注解 @SpringBootApplication 组合了@Configuration, @EnableAutoConfiguration, @ComponentScan,用作Spring Boot应用的启动入口。
📦 容器与组件注册 @Component, @Service, @Repository, @Controller, @RestController, @Configuration, @Bean, @Scope, @Lazy 用于声明一个类由Spring IoC容器管理,并定义其生命周期和作用域。
🔗 依赖注入 @Autowired, @Qualifier, @Primary, @Resource, @Inject, @Value 用于向Spring管理的Bean中自动注入所需的依赖或配置值。
🌐 Web 层 (Spring MVC) @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @RequestParam, @PathVariable, @RequestBody, @ResponseBody, @ControllerAdvice, @ExceptionHandler 用于处理HTTP请求、解析参数、返回数据以及进行统一的异常和全局配置。
🗄️ 数据访问与事务 @Transactional, @EnableTransactionManagement, @Entity, @Table, @Id, @Column 用于声明式事务管理和(当使用JPA/Hibernate时)对象-关系映射。
⚙️ 配置与属性 @ConfigurationProperties, @PropertySource, @Profile, @Conditional 用于绑定外部配置文件、加载属性文件、实现环境隔离和条件化配置。
⏱️ 任务与异步 @Scheduled, @EnableScheduling, @Async, @EnableAsync 用于声明定时任务和创建异步执行的方法。
✂️ 切面编程 (AOP) @Aspect, @Before, @After, @Around, @Pointcut, @EnableAspectJAutoProxy 用于定义横切关注点(如日志、权限),实现业务逻辑与非业务逻辑的解耦。
✅ 校验注解 (Bean Validation) @NotNull, @NotEmpty, @NotBlank, @Size, @Min, @Max, @Email, @Pattern 用于对Java Bean的属性进行声明式校验,常与@Valid@Validated配合使用。

事务

事务主要是基于第三方框架协同实现的,如mysql、redis等。 事务的传播行为:

需求场景 推荐传播行为
绝大多数业务方法 REQUIRED
需要独立事务,失败不影响外层 REQUIRES_NEW
只读查询,有事务更好,没有也行 SUPPORTS
强制有事务,否则报错 MANDATORY
绝对不能有事务(例如性能特殊操作) NEVER
不希望运行在事务中,且挂起已有事务 NOT_SUPPORTED
希望局部回滚,外层可以继续 NESTED(需底层支持)

事务失效

SpringMvc

Spring MVC 的诞生并非偶然,它是为了系统性地解决早期 Java Web 开发中的混乱与耦合问题,借鉴经典软件工程思想而诞生的产物。

理论基础:MVC 模式之父

Spring MVC 的理论基础,源于挪威计算机科学家 Trygve Reenskaug (Trygve Mikkjel Heyerdahl Reenskaug)在 1978 年 提出的 MVC(模型-视图-控制器) 模式。该模式的核心思想是将应用程序划分为三个核心部分:

  • 模型(Model):封装数据和业务逻辑。
  • 视图(View):负责将数据展示给用户。
  • 控制器(Controller):接收用户输入,更新模型,并协调模型和视图。

将 MVC 模式引入 Web 开发,就是为了实现关注点分离,让代码结构更清晰、更易维护。

历史必然:由"混乱"催生的"解耦"

在 Spring MVC 出现之前,Java Web 开发主要依赖 ServletJSP,并经历了几个阶段,但都存在严重的耦合问题。

  • Model1时代:职责混乱。 开发时在 JSP 页面中同时编写 HTML、Java 和 SQL 代码,最终代码难以维护。
  • Model2时代:初步分离。 虽然通过 JSP(视图)和 Servlet(控制器)实现了关注点分离,但开发者仍不得不在代码中硬编码视图跳转路径和手动解析请求参数,代码臃肿、复用性差。 破局者降临:Rod Johnson 与 Spring MVC 为解决早期开发的混乱,Spring 的创始人 Rod Johnson 引入了新思想。
  1. Spark of Inspiration: 奠定思想的著作 2002 年,Rod Johnson 在其著作 《Expert One-on-One J2EE Design and Development》 中,倡导 J2EE 实用主义的设计思想,并提出使用轻量级的 Interface 21 框架来取代臃肿的 EJB。

  2. The Birth of Spring: 核心能力的崛起 2003 年,以 Interface 21 框架为基础,Spring 框架正式开源。

    • 它引入的 IoC(控制反转)DI(依赖注入),解决了"谁来管对象"的问题。
    • 它引入的 AOP(面向切面编程),解决了"横切逻辑如何复用"的问题。
  3. Solution to Chaos: Spring MVC 的诞生 在 IoC 和 AOP 两大核心能力的基础上,开发者按照 MVC 思想设计了一个 Web 框架 ------这就是 Spring MVC。它基于 Servlet API 构建,并被命名为 "Spring Web MVC"。

    • 🎯 核心"总指挥":DispatcherServlet 所有请求首先到达这个"前端控制器",它负责协调其他组件共同完成请求处理,极大简化了 Web 层的开发。
    • 🎯 优雅的"指挥官":HandlerMapping 与 HandlerAdapter
      • HandlerMapping 负责维护一个"请求URL → 具体方法"的映射表。
      • HandlerAdapter 负责调用具体的方法,并适配不同类型的控制器。
    • 🏆 一决雌雄:Spring MVC vs. Struts Spring MVC 的兴起也与它战胜了当时的霸主 Struts 有关。
      • Struts 1.x 的局限:控制器必须继承特定类,导致侵入性强、不易测试。
      • Spring MVC 的优势@Controller@RequestMapping 注解的引入,使方法间的映射清晰灵活,更轻量、易于测试,并与 Spring 核心完美集成。 最终,Spring MVC 凭借更先进、灵活的设计哲学,赢得了社区的青睐,成为了事实标准。

演进之路:从 XML 到注解自动化

为了更好地适应开发者的需求,Spring MVC 经历了关键的演进:

  • 早期版本(Spring 1.x - 2.x) :主要使用 XML 配置,开发者需要在 XML 文件中显式声明请求 URL 与 Controller 的对应关系。
  • 现代化形态(Spring 2.5+ 到 Spring Boot) :2007 年开始逐渐引入 注解驱动 的开发模式。通过 @Controller@RequestMapping 等注解,大大简化了配置。Spring Boot 的"约定大于配置"理念更是极大地简化了项目构建和部署流程,让开发者能更专注于 Spring MVC 的核心业务逻辑本身。

总的来说,Spring MVC 的出现是软件工程思想演进(MVC 模式)与开发者对更高效、更解耦开发方式追求(替代 EJB 和 Struts)共同作用的结果。

sprngboot

启动流程

相关推荐
爱勇宝1 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries1 小时前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术3 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎4 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode4 小时前
Redis 在生产项目的使用
前端·后端
用户559822481224 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode4 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战4 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha4 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn4 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端