44Spring

对Spring核心思想的理解

Spring通过IoC,DI,AOP三大核心思想,实现了轻量级,高内聚低耦合的企业级应用开发架构,成为Java生态中不可或缺的基石

image.png

IOC

谈谈自己对Spring IoC的了解

(1)IoC基础定义与思想本质Ioc是设计思想,并未Spring特有,核心是将程序中手动创建、管理对象的控制权,从业务代码转移到外部环境控制反转拆解控制:指对象生命周期的管理权(包含实例化、依赖组装、销毁等)反转:指该控制权从”开发者手动编码”转移到”外部IoC容器”

什么是SpringBean

Bean就是被IoC容器所管理的对象我们需要告诉IoC容器帮助我们管理哪些对象,这个是通过配置元数据来定义的,可以通过XML文件、注解或Java配置类

Bean是线程安全吗

Bean是否线程安全,核心取决于Bean的作用域和Bean是否包含可变状态

(1)核心概念

Bean作用域

Bean的实例数量,生命周期,可见性

Bean状态

无状态Bean:不包含可变成员变量

有状态Bean:包含可变成员变量,不同线程调用时可能修改同一实例的变量,引发数据冲突

(2)不同作用域的Bean线程安全

prototype

由于每次通过容器获取Bean都会创建新的实例,因此不同线程,不同请求获取的Bean都是独立的对象,因此不存在线程安全问题

singleton

无状态单例Bean:线程安全,成员变量不共享,不可变

有状态单例Bean:线程不安全,成员变量共享,可变(多线程调用共享变量可能导致数据错乱)

(3)解决方案

设计为无状态Bean

移除Bean中的可变成员变,将状态数据通方法参数传递,或存储到外部容器,避免实例变量共享

使用ThreadLocal实现线程隔离

将可变成员变量存储到ThreadLocal中,每个线程拥有独立的变量副本,ThreadLocal本质是线程私有存储,

ThreadLocal需在线程结束时调用remove,避免发生内存泄漏

适用于状态仅在当前线程内有效的场景(如HTTP请求内的临时数据)

使用同步机制(兜底)

判定Spring Bean是否线程安全1)先看作用域:prototype必定安全,singleton需进一步判定状态2)再看状态:无状态singleton安全,有状态singleton不安全3)解决方案优先级:无状态设计 > ThreadLocal线程隔离 > 同步机制

Bean的作用域有哪些

单例模式

Java 设计模式之单例模式(详细解析) - 技术栈

(1)作用域的概念

Bean作用域本质定义了Spring容器管理Bean实例的规则

容器会创建一个还是多个Bean

Bean实例的生命周期何时开始、何时销毁

Bean实例在哪些场景下可见

(2)Spring内置Bean作用域

通用作用域(非Web环境也可用)

singleton

核心规则:IoC容器中仅存在一个Bean实例,所有对该Bean的请求都会返回同一个实例

生命周期:Bean实例在容器启动时创建,随容器关闭而销毁,生命周期与容器一致

注意事项:单例Bean是线程共享的,若存在成员遍历且被修改,会引发线程安全问题,可通过@Lasy设置懒加载:容器启动时不创建实例,在第一次请求时才创建

prototype

核心规则:每次请求Bean时,容器都会创建一个新的实例

生命周期:Bean实例在请求时创建,容器仅负责实例化,不负责销毁,销毁由JVM垃圾回收机制处理

注意事项:频繁创建prototype Bean会增加内存消耗,需避免在高并发场景下滥用

Web专属作用域

request

核心规则:每一个HTTP请求对应一个Bean实例,仅在当前请求内有效,不同请求的实例独立,Bean内存储当前请求的临时数据

生命周期:随HTTP请求创建,请求结束后销毁

session

核心规则:每一个用户会话对应一个Bean实例, 同一用户会话内共享,不同会话独立,存储用户的登录状态或偏好设置等

生命周期:随用户第一次请求创建,会话过期/关闭后销毁

application

核心规则:整个Web应用对应一个Bean实例,所有用户共享,存储全局配置数据(如应用版本、全局计数器等)

生命周期:随Web应用启动创建,应用关闭后销毁

websocket

核心规则:每一个WebSocket会话对应一个Bean实例,仅在当前WebSocket连接内有效,WebSocket通信的实时聊天数据

生命周期:随WebSocket连接创建,连接关闭后销毁

(3)自定义作用域

Spring允许开发者自定义作用域,满足特殊业务需求

步骤

实现Scope接口

重写get(获取实例),remove(移除实例),registerDestructionCallbac(注册销毁回调)

注册自定义作用域

通过ConfigurableBeanFactory的registerScope方法,将自定义作用域注册到Spring容器

使用自定义作用域

通过@Scope标注Bean,容器按自定义规则管理实例

什么是Spring AOP

AOP:能够将哪些与业务无关,却被各种业务模块所共同调用的通用逻辑或责任(日志记录,事务处理,权限控制)封装起来,便于减少系统的重复代码,降低模块间的耦合度,有利于代码的维护与拓展Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP会使用JDK Proxy(java.lang.reflect.Proxy),区创建代理对象使用JDK Proxy实现动态代理的前提是业务类必须实现某一接口

image.png

AOP常见的通知类型有哪些

(1)Before触发时机在目标对象的方法调用之前触发可获取信息及操作权限只能获取方法的签名信息(方法名,参数等),但无法获取方法的返回值,也不能对方法的执行进行干预.应用场景记录方法调用日志、在用户执行关键操作前进行权限检查(2)After触发时机目标对象方法执行完成之后触发,且无论目标方法执行过程中是否出现异常都会触发.可获取信息及操作权限可以获取到方法的执行结果(无论是否有异常),但无法对结果进行修改应用场景无论业务操作是否成功的一些场景,如关闭文件流、数据库连接等资源(3)AfterReturning触发时机只有当目标对象方法调用完成且没有抛出异常,在返回结果值之后触发可获取信息及操作权限可以获取到目标方法正常执行后的返回值,并且可以对返回值进行修改,然后再返回给调用者应用场景对业务方法返回的数据进行格式转换、脱敏处理;缓存业务方法的返回结果,提高后续查询效率等(4)AfterThrowing触发时机当目标对象的方法运行中抛出异常后触发,它与AfterReturin是互斥的可获取信息及操作权限能够获取到方法执行过程抛出的异常对象,通过对异常类型的判断来进行不同的异常处理应用场景记录异常日志,向用户发送友好的错误提升等(5)Around触发时机可以在目标对象方法调用前后进行编程式控制,它的执行时机是在目标方法调用之前先进入环绕通知,在环绕通知中决定是否调用目标方法,目标方法调用完成后再回到环绕通知继续执行后续代码它是功能最强大的通知类型,比如可以在方法调用前开启事务,方法调用成功后提交事务,方法调用失败时回滚事务可获取信息及操作权限能够获取到目标对象、要执行的方法以及方法的参数信息,并且可以决定目标方法是否执行,还能修改方法的返回值或者捕获异常并处理应用场景实现事务管理;实现性能监控,统计方法的执行时机

动态代理和静态代理的区别

(1)什么是代理

代理是一种经典设计模式,核心目的是为目标对象提供访问控制的中间层,从而解耦调用方与 目标对象的直接解耦在实现上,代理类和目标类需遵循相同的接口规范,确保代理能无缝转发对目标类方法的调用

(2)区别

静态代理由程序员手动编写或通过特定工具生成,在编译阶段就确定了代理关系仅代理单个类,代码结构固定动态代理在运行时通过反射机制动态生成代理类,无需提前编写固定代理代码.可同时代理一个接口下的多个实现类,灵活性高.

Spring进阶

Spring为什么用三级缓冲解决循环,一级、二级不行吗

(1)一级缓存

一级缓存只能存储完全初始化的成品Bean或半成品Bean,显然上述步骤的某一步是缺失的,不能解决循环依赖问题

(2)二级缓存

如果将一级缓存存成品,二级存半成品可以吗问题出在初始化过程中的AOP代理导致对象不一致问题:当A将半成品的A暴露给B时,B将半成品的A属性注入,然后进行A的初始化,生成了A的代理对象,最终导致B中的A与最终的AProxy不一致加入三级缓存,通过工厂实现按需生成代理.

@Lasy能否解决循环依赖

(1)@Lasy注解作用

加上@Lasy后,Bean仅在第一次被使用(被其它Bean依赖或调用)才创建

在循环依赖这个特殊场景导致它在容器启动时创建,但仍符合在第一次被使用时创建”的规则

(2)如何解决循环依赖

image.png

Spring容器里面存的是什么

Spring容器中存储的核心是Bean对象,而这些Bean本质上是对应用中各类 组件的封装比如,一个用户服务类是业务组件,一个数据库访问类是数据层组件,它们都会以Bean的形式存在于Spring容器中,实现了对应用中各类组件的集中管控.

Spring中,Bean加载和销毁前后,如果想要实现某些逻辑,可以怎么做

可以通过xml、注解、实现接口实现某一具体Bean在初始化完成后和销毁开始前执行自定义逻辑

实现接口

初始化回调:实现InitializingBean接口并重写afterPropertiesSet

销毁回调:实现DisposableBean接口并重写destroy

import org.springframework.beans.factory.InitializingBean;

@Component

public class MyBean implements InitializingBean {

​ @Override

​ public void afterPropertiesSet() throws Exception {

​ // Bean初始化完成后执行的逻辑(如资源初始化、参数校验)

​ System.out.println(“Bean初始化完成,执行自定义逻辑”);

​ }

}

import org.springframework.beans.factory.DisposableBean;

@Component

public class MyBean implements DisposableBean {

​ @Override

​ public void destroy() throws Exception {

​ // Bean销毁前执行的逻辑(如资源释放、连接关闭)

​ System.out.println(“Bean销毁前,执行自定义逻辑”);

​ }

}

注解

@PostConstruct注解:在Bean的方法上标注该注解,该方法会在Bean初始化完成后执行

@PreDestroy注解:在Bean方法上标注@PreDestroy,该方法会在Bean销毁前执行

运行代码

import javax.annotation.PostConstruct;

@Component

public class MyBean {

​ @PostConstruct

​ public void init() {

​ // Bean初始化完成后执行的逻辑

​ System.out.println(“通过@PostConstruct执行初始化逻辑”);

​ }

}

import javax.annotation.PreDestroy;

@Component

public class MyBean {

​ @PreDestroy

​ public void destroy() {

​ // Bean销毁前执行的逻辑

​ System.out.println(“通过@PreDestroy执行销毁逻辑”);

​ }

}

SpringMVC

Spring MVC的处理流程说一下

image-20251126213013309

事务传播机制是指在一个事务方法被另一个事务方法调用时,如何管理和协调事务的边界和行为。

事务传播机制的基本概念

在编程和数据库管理中,事务是一个逻辑单位,确保一组操作要么全部成功,要么全部失败。事务传播机制主要解决多个事务方法相互调用时,事务如何在这些方法之间传播的问题。Spring框架提供了七种事务传播行为,分别是:

  1. REQUIRED:如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务。这是最常用的传播行为,适用于大多数业务逻辑场景。
  2. SUPPORTS:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行。适用于读取数据时,可以不必加事务,但如果有事务则可以加。
  3. MANDATORY:必须在现有事务中执行,如果没有事务,则抛出异常。适用于需要依赖现有事务的操作。
  4. REQUIRES_NEW:无论是否存在当前事务,都会创建一个新事务。如果有现有事务,则挂起该事务,待新事务完成后再恢复。适用于独立执行的事务,通常用于日志记录等场景。
  5. NOT_SUPPORTED:如果当前有事务,则挂起该事务,以非事务方式执行。适用于明确不需要事务控制的操作,如查询操作。
  6. NEVER:要求方法不能在事务中执行。如果当前有事务,则抛出异常。适用于一些完全不允许在事务环境中执行的操作。
  7. NESTED:如果当前存在事务,则在嵌套事务中执行;如果没有事务,则创建一个新事务。适用于需要在现有事务中执行的操作,但又希望能够单独回滚的场景。

@Lazy注解解决循环依赖的底层原理

@Lazy注解是Spring框架提供的一种解决循环依赖问题的方式。当两个或多个Bean之间存在循环依赖时,可以使用@Lazy注解延迟初始化其中一个Bean,从而打破循环依赖。

具体原理如下:

  1. 当Spring容器启动时,会解析并创建所有的Bean定义,但不会立即初始化Bean实例。
  2. 当遇到循环依赖时,Spring会创建一个代理对象来代替其中一个Bean的实例。
  3. 通过@Lazy注解,可以指定某个Bean在第一次被使用时才进行初始化,而不是在容器启动时立即初始化。
  4. 当另一个Bean依赖于被@Lazy注解修饰的Bean时,Spring会返回一个代理对象,而不是实际的Bean实例。
  5. 当代理对象被调用时,Spring会检查是否需要初始化被@Lazy注解修饰的Bean。
  6. 如果需要初始化,Spring会解析并创建被@Lazy注解修饰的Bean,并将其注入到代理对象中。
  7. 这样,循环依赖问题得到了解决,每个Bean都能够正确地获取到对方的实例。

需要注意的是,@Lazy注解只能解决单例Bean之间的循环依赖问题,因为单例Bean在容器启动时就会被创建。对于原型(prototype)作用域的Bean,@Lazy注解不起作用,因为原型Bean每次获取时都会创建一个新的实例。

ewnd


44Spring
http://example.com/2025/11/24/44Spring/
作者
無鎏雲
发布于
2025年11月24日
许可协议