当前位置: 首页 > news >正文

Spring知识点梳理

一、Spring(Spring Framework)

1、IOC(控制反转)

1)什么是IOC控制反转?

为了解藕,有反转就有“正转”,“正转”就是程序员手动 new对象;“反转”就是将对象的创建、对象的依赖关系都交给框架(如Spring的IOC容器)管理;Spring主要通过“依赖注入(DI)”实现IOC。

IOC是设计思想,简单工厂、通过反射实现DI(依赖注入)。

2)依赖注入(DI)和自动装配

@Mapper    
public interface AbcDao {// 方法定义
}
@Service
public class SomeServiceImpl {@Autowiredprivate AbcDao abcMapper;// 使用dao的方法
}
  • AbcDao使用@Mapper注解、SomeServiceImpl使用@Service注解,Spring会自动创建这个dao的代理对象,这个代理对象会被自动注册到Spring容器中成为一个Bean,即自动装配。
  • SomeServiceImpl 中使用@Autowired进行依赖注入,在SomeServiceImpl 中可以随时通过abcMapper调用dao层中的方法,即依赖注入
  • 自动装配侧重Bean的创建与注册(如@Mapper生成Bean),依赖注入侧重Bean的使用关系(如@Autowired注入字段)
  • 自动装配 + 依赖注入,共同实现了 IOC(控制反转)思想

3)依赖注入的3种方式

注解注入、Setter方法注入、构造器注入

@Mapper    
public interface DDeptConsumableDao {// 方法定义
}
@Service
public class SomeServiceImpl {// 使用注解方式,实现“依赖注入”@Resourceprivate DDeptConsumableDao dDeptConsumableDao;/** ---------------------------------------------------------------- */// 使用Setter方式,实现“依赖注入”private DDeptConsumableDao dDeptConsumableDao;@Autowiredpublic void setDDeptConsumableDao(DDeptConsumableDao dDeptConsumableDao) {this.dDeptConsumableDao = dDeptConsumableDao;}/** ---------------------------------------------------------------- */// 使用构造器的方式,实现“依赖注入”private final DDeptConsumableDao dDeptConsumableDao;public SomeServiceImpl(DDeptConsumableDao dDeptConsumableDao) {this.dDeptConsumableDao = dDeptConsumableDao;}
}
(1)为什么推荐“构造器”的注入方式?
  1. 不可变性(final支持):被final关键字修饰,不可变
  2. 提前暴露循环依赖问题:注解注入,存在“循环依赖”问题(A依赖B,B依赖A),使用构造器注入,在项目启动时,会立刻抛出异常(BeanCurrentlyInCreationException)
  3. 避免空指针异常(NPE):避免“空指针异常”,启动项目的时候,例如dao层没有设置@Mapper注解被注入到容器中,会立马抛出异常
  4. 显式声明依赖关系:通过构造器,一目了然看出所有依赖
  5. 更好的可测试性:无需Spring容器,直接通过new创建对象进行单元测试
(2)@Autowired、@Resource、@Inject等注解注入,有何区别?
  1. @Autowired是Spring自带的,只支持Spring容器,@Resource是JSR250(jdk)规范实现的,可以脱离Spring,@Inject是JSR330规范实现的
  2. @Autowired、@Inject用法基本一样,不同的是@Inject没有required属性
  3. @Autowired、@Inject是默认按照类型(byType)匹配的,@Resource是按照名称(byName)匹配的
  4. @Autowired 按照名称匹配需要和@Qualifier一起使用;@Inject需配合@Named一起使用;@Resource则通过name进行指定
  5. @Autowired可以修饰构造器,@Resource不行
  6. 如果某个bean为null,@Autowired会直接报错“bean不存在”,@Resource不

2、AOP(切面编程)

1)什么是AOP?(切面编程)

AOP切面编程:类似于一种拦截器,像一把刀一样选择一个方法为切入点,在这个方法执行的前后可以设置前置通知、后置通知、异常通知等,这些通知可以是对业务进行判断、日志记录、事务回滚等操作。
在这里插入图片描述

2)AOP的通知类型

通知类型注解执行时机能否阻止目标方法执行
前置通知@Before目标方法执行前❌ 不能
后置通知@AfterReturning目标方法正常完成后❌ 不能
异常通知@AfterThrowing目标方法抛出异常后❌ 不能
最终通知@After目标方法执行后(无论是否成功)❌ 不能
环绕通知@Around包围目标方法执行✅ 能(通过proceed()控制)

3)动态代理(jdk、Cglib)

文档链接:
接口使用jdk代理

(1)什么是Cglib?

CGLIB 就是第三方 Jar 包,它的作用是在程序运行时,动态生成新的 Java 类(目标类的子类),从而实现代理功能(比如在不修改原有代码的情况下,给某个类的方法增加额外逻辑,如日志、事务、权限控制等)。

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
(2)JDK动态代理与CGLIB动态代理的区别
  1. 实现原理:
    • jdk:基于Java反射机制,通过实现InvocationHandler接口拦截方法调用
    • cglib:通过生成目标类的子类实现代理
  2. 适用范围:
    • jdk:只能代理实现了接口的类
    • cglib:可以代理未实现任何接口的类(除了final类、static、final方法)
  3. 方法调用机制:
    • jdk:通过反射机制调用目标方法
    • cglib:通过FastClass机制直接调用目标方法,避免了反射调用
(3)为什么SpringBoot2.0之后,默认是cglib的动态代理?
  1. 更通用的代理机制:CGLIB可以代理没有实现接口的类,而JDK动态代理只能代理实现了接口的类
    • cglib是实现目标类的子类,但是final修饰的类无法被继承,static修饰的方法无法被重写
  2. 简化开发体验:开发人员不需要刻意设计接口和实现类的结构来支持AOP,可以更专注于业务逻辑。
  3. 跨JDK版本的一致性:cglib是一个第三方的jar包,不会因为jdk版本的不同而有显著的变化
  4. 性能考虑:虽然早期JDK动态代理性能不如CGLIB,但这个差距在现代JDK版本中已经缩小。不过,CGLIB在大量方法调用场景下仍可能有一定优势,因为它避免了反射调用的开销。
  5. 在被使用到时候,生成对应的子类,但是避免了反射,调用起来会很快

4)Spring AOP 与 AspectJ(了解即可)

是2种不同的AOP框架:

特性SpringAOPAspectJ
实现方式基于动态代理(JDK/CGLIB)编译时/加载时织入(字节码增强)
依赖需 Spring 容器独立,不依赖 Spring
功能范围仅拦截 Spring Bean 的方法调用支持 方法、构造器、字段、静态块等
性能运行时代理,稍慢编译期优化,更快
适用场景简单 AOP(日志、事务等)复杂 AOP(非 Spring 对象、细粒度控制)

一句话总结
🔹 Spring AOP = 轻量级,易用,但功能有限
🔹 AspectJ = 功能全,性能强,但更复杂

3、Spring Beans

1)什么是Bean?

Bean就是Spring框架帮忙创建和管理的“对象镜像”、“clazz对象”,即代理对象;例如User类,一般由程序员手动new出来的是user_1这个实例对象,完全由程序员控制,自己创建、自己赋值、自己销毁,和Spring无关。

User user_1 = new User();  // 生死由程序员负责

由Spring创建出来了一个User的实例对象“user_2”,这个“user_2”就称为Bean,功能、作用和user_1一模一样,不同点就是Bean的生命周期由Spring管理,与程序员无关,程序员只管使用这个Bean即可。

@Autowired
User user_2;  // 由Spring创建、组装、管理生命周期,你只需要“用”,不用管它怎么来的。

2)Bean的单例和多例

单例Bean(默认):

  1. Spring默认给每个Bean定义一个共享实例(类似全局镜像)。
    a. 例如:@Autowired User user_2 代码执行十次,拿到的都是同一个对象,除非用@Scope(“prototype”)改为多例
  2. 单例在高并发是不安全的,如果想安全,可以使用ThreadLocal

多例Bean:

  1. @Scope(“prototype”),每次获取(如@Autowired)时,Spring都会创建一个全新的实例,就像你每次new一个对象一样。
  2. 适用场景:
    a. 计数器(Counter):如果计数器是单例的,所有用户会共享同一个计数,导致数据混乱
    b. 短生命周期的对象(临时对象),用完即销毁

3)Bean的生命周期

CSDN文档(5、7、10步剖析Bean的生命周期):
Bean的生命周期(五步、七步、十步法剖析)

(1)什么是Bean的生命周期?为什么要知道?

对象从创建开始到最终销毁的整个过程;
可以在生命周期的某个节点,执行特点的业务逻辑

(2)5步分析法
  1. 第一步:实例化Bean(调用无参构造器)
  2. 第二步:Bean属性赋值(调用set方法)
  3. 第三步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
  4. 第四步:使用Bean
  5. 第五步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写)
    在这里插入图片描述

实现代码:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;@Component // 注入到spring容器
public class BeanUser {private String userName;// 无参构造器public BeanUser() {System.out.println("第1步,无参构造器,实例化Bean");}@Value("张三(Value注解默认设置用户名)")public void setUserName(String userName) {System.out.println("第2步,setter方法给对象属性赋值");this.userName = userName;}/*@PostConstruct 注解:在Bean初始化完成后(即依赖注入完成,但还未被使用前)自动执行,用途:用于自定义初始化逻辑(如数据加载、资源初始化等)*/@PostConstructpublic void initBean() {    // 方法名可自定义System.out.println("第3步,初始化Bean");}/*@PreDestroy 注解:标记一个方法,在 Bean 被销毁前(如容器关闭时)自动执行用途:用于清理资源(如关闭数据库连接、释放文件句柄等)*/@PreDestroypublic void destroyBean() { // 方法名可自定义System.out.println("第5步:销毁Bean");}@Overridepublic String toString() {return "BeanUser{" + "userName='" + userName + '\'' +  '}';}}

@SpringBootApplication
public class SpringBootTest2Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringBootTest2Application.class, args);BeanUser beanUser = context.getBean(BeanUser.class);System.out.println("第4步,使用Bean - " + beanUser);context.close();    // 触发销毁方法}}
// 输出内容:1步,无参构造器,实例化Bean2步,setter方法给对象属性赋值
第3步,初始化Bean4步,使用Bean - BeanUser{userName='张三(Value注解默认设置用户名)'}5步:销毁Bean

** 注意:**

  1. 只有正常关闭spring容器(调用close方法),bean的销毁方法才会被调用
  2. @PostConstruct 注解指定“初始化方法”,@PreDestroy 注解指定“销毁方法”
(3)7步分析法

在“5步分析法”中,第3步是初始化Bean,如果还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”;需要编写一个类实现BeanPostproccessor接口,并重写里面的befor和after方法。
就能将Bean的生命周期分为7步:

  1. 第1步:实例化Bean(调用无参构造器)
  2. 第2步:Bean属性赋值(调用set方法)
  3. 第3步:执行“Bean后处理器”的before方法
  4. 第4步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
  5. 第5步:执行“Bean后处理器”的after方法
  6. 第6步:使用Bean
  7. 第7步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写)

在这里插入图片描述

代码示例:在“5步分析法”示例代码的基础上,添加 Bean后处理器

import com.example.springboot_test2.entity.BeanUser;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;@Component
public class LogBeanPostProcessor implements BeanPostProcessor {/*** @param bean  创建的Bean对象* @param beanName  Bean的名字*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {//  BeanPostProcessor 会作用于 Spring 容器中的每一个 Bean,//      包括 Spring 自身的一些内置 Bean,如果不做if判断,会看到大量重复的输出if (bean instanceof BeanUser) {  // 只处理 BeanUser 类型的 BeanSystem.out.println("Bean后处理器的before方法执行,即将开始初始化");}// return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {//  BeanPostProcessor 会作用于 Spring 容器中的每一个 Bean,//      包括 Spring 自身的一些内置 Bean,如果不做if判断,会看到大量重复的输出if (bean instanceof BeanUser) {  // 只处理 BeanUser 类型的 BeanSystem.out.println("Bean后处理器的after方法执行,已完成初始化");}// return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);return bean;}
}
// 输出结果:1步,无参构造器,实例化Bean2步,setter方法给对象属性赋值
Bean后处理器的before方法执行,即将开始初始化
第3步,初始化Bean
Bean后处理器的after方法执行,已完成初始化
第4步,使用Bean - BeanUser{userName='张三(Value注解默认设置用户名)'}5步:销毁Bean
(4)10步分析法

比上面的“7步分析法”多了哪3步:

  1. 在“Bean后处理器”before方法之前干了什么事?
    • 检查Bean是否实现了Aware相关的接口,如果实现了接口则调用这些接口中的方法;调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。
  2. 在“Bean后处理器”before方法之后干了什么事?
    • 检查Bean是否实现了InitializingBean接口,如果实现了,则调用接口中的方法。
  3. 使用Bean之后,或者说销毁Bean之前干了什么事?
    • 检查Bean是否实现了DisposableBean接口,如果实现了,则调用接口中的方法

在这里插入图片描述

**总结:**添加的这3个点位的特点,都是在检查你这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法!

**Aware相关的接口包括:**BeanNameAware、BeanClassLoaderAware、BeanFactoryAware

  1. 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
  2. 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
  3. 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。

测试“10步分析法”,可以让User类实现5个接口,并实现所有方法:
①BeanNameAware
②BeanClassLoaderAware
③BeanFactoryAware
④InitializingBean
⑤DisposableBean

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;@Component
public class BeanUser implements BeanNameAware, BeanClassLoaderAware,BeanFactoryAware, InitializingBean, DisposableBean {private String userName;// 无参构造器public BeanUser() {System.out.println("第1步,无参构造器,实例化Bean");}@Value("张三(Value注解默认设置用户名)")public void setUserName(String userName) {System.out.println("第2步,setter方法给对象属性赋值");this.userName = userName;}/*@PostConstruct 注解:标记一个方法,在 Bean 初始化完成后(即依赖注入完成,但还未被使用前)自动执行,用途:用于自定义初始化逻辑(如数据加载、资源初始化等)*/@PostConstructpublic void initBean() {    // 方法名可自定义System.out.println("第3步,初始化Bean");}/*@PreDestroy 注解:标记一个方法,在 Bean 被销毁前(如容器关闭时)自动执行用途:用于清理资源(如关闭数据库连接、释放文件句柄等)*/@PreDestroypublic void destroyBean() { // 方法名可自定义System.out.println("第5步:销毁Bean");}@Overridepublic String toString() {return "BeanUser{" +"userName='" + userName + '\'' +'}';}/*** setBeanName()、setBeanClassLoader()、setBeanFactory(),*  这3个方法,即在“Bean后处理器”before方法之前干了什么事?*/@Overridepublic void setBeanName(String name) {System.out.println("Bean的名字:" + name);}@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {System.out.println("类加载器:" + classLoader);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("Bean工厂:" + beanFactory);}/*** afterPropertiesSet(),在“Bean后处理器”before方法之后干了什么事*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean afterPropertiesSet方法执行了");;}/*** destroy(),销毁Bean之前干了什么?*/@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean destroy方法执行了");}}
// 输出内容1步,无参构造器,实例化Bean2步,setter方法给对象属性赋值
Bean的名字:beanUser
类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
Bean工厂:org.springframework.beans.factory.support.DefaultListableBeanFactory@38145825:......
Bean后处理器的before方法执行,即将开始初始化
第3步,初始化Bean
InitializingBean afterPropertiesSet方法执行了
Bean后处理器的after方法执行,已完成初始化
第4步,使用Bean - BeanUser{userName='张三(Value注解默认设置用户名)'}5步:销毁Bean
DisposableBean destroy方法执行了
(5)Bean生命周期的总结
  1. 第1步:实例化Bean(调用无参构造器)
  2. 第2步:Bean属性赋值(调用set方法)
  3. 第3步:“Bean后处理器”的before方法之前,做了什么
  4. 第4步:执行“Bean后处理器”的before方法
  5. 第5步:“Bean后处理器”的before方法之后,做了什么
  6. 第6步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
  7. 第7步:执行“Bean后处理器”的after方法
  8. 第8步:使用Bean
  9. 第9步:销毁Bean之前,做了什么
  10. 第10步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写)

4)三级缓存,解决循环依赖问题

  • Spring只是解决了单例模式下属性循环依赖问题
    • A依赖B,B又依赖A:创建A → 需要注入B → 创建B → B需要注入A → A还未创建完成…
  • Spring为了解决单例的循环依赖问题,使用了三级缓存
(1)三级缓存

bean的诞生,从三级 -> 二级 -> 一级

  1. 三级缓存(singletonFactories):Bean工厂,储存new出来的原始对象
    a. 就是自己的对象,没有ioc、代理这些东西
  2. 二级缓存(earlySingletonObjects):存储半成品Bean,这些Bean已创建但还未完全初始化,用于提前暴露创建中的Bean
    a. 提前暴露创建中的Bean:指的就是已经创建出对象实例(即已经调用了构造方法完成对象实例化),但尚未完全 完成依赖注入(属性赋值)和初始化(如调用initMethod、afterPropertiesSet等)的Bean。
  3. 一级缓存(singletonObjects):存储完全初始化好的单例Bean,这些Bean已经完成了属性注入和初始化方法调用
1、什么是“提前暴露创建中的Bean”?

具体来说,Bean的创建过程大致分为三个阶段:

  1. 实例化:调用构造方法创建对象实例
  2. 属性赋值:注入依赖的其他Bean或属性
  3. 初始化:执行初始化方法等
    提前暴露创建中的Bean:就是处于“二级缓存”中,但是 属性赋值(依赖注入)未完全完成
(2)三级缓存,如何解决“循环依赖”问题

假设A和B互相依赖,先创建A:

  1. 创建A的空对象
  2. 把A的工厂放入三级缓存
  3. 给A填充属性,发现需要B
  4. 去创建B对象
  5. 把B的工厂放入三级缓存
  6. 给B填充属性,发现需要A
  7. 关键步骤:从三级缓存获取A的工厂,创建早期版本的A,并把A升级到二级缓存
  8. 用二级缓存中的A对象注入给B
  9. B完成初始化,放入一级缓存
  10. 继续A的属性填充,注入已完成的B
  11. A完成初始化,放入一级缓存

总结:
Spring先暴露空壳对象A到三级缓存,让B能引用到它完成初始化,再用完整的B去完成A的初始化,从而打破循环依赖的死锁
在这里插入图片描述

(3)Spring为什么不能解决单例之外的循环依赖
  1. 原理冲突:三级缓存依赖于Bean的复用性,而非单例Bean(如prototype作用域)每次请求都会创建新实例,无法复用缓存中的对象
  2. 生命周期不同:非单例Bean的生命周期由使用者管理,Spring不会缓存它们,每次获取都是新创建的对象
  3. 资源考量:缓存所有的prototype Bean会造成内存浪费,因为它们可能只使用一次

4、Spring事务

底层原理就是AOP切面编程,AOP的底层原理就是“动态代理”

1)事务的实现方式

(1)编程式事务

程序员手写代码控制事务:开启事务 --> 提交事务 --> 回滚事务

@RestController
public class UserController {@Resourceprivate UserService userService;// JDBC 事务管理器@Resourceprivate DataSourceTransactionManager dataSourceTransactionManager;// 定义事务属性@Resourceprivate TransactionDefinition transactionDefinition;@RequestMapping("/sava")public Object save(User user) {// 开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 插⼊数据库int result = userService.save(user);// 提交事务dataSourceTransactionManager.commit(transactionStatus);// // 回滚事务// dataSourceTransactionManager.rollback(transactionStatus);return result;}
}
(2)声明式事务

在需要的方法/类上添加 @Transactional注解即可,⽆需⼿动开启事务和提交事务,进入方法时⾃动开启事务,方法执行完会⾃动提交事务,如果中途发生了没有处理的异常会⾃动回滚事务。

  • 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法
  • 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效

2)事务的传播行为

  1. REQUIRED(默认):有副本就一起刷,没有就开新副本
  • A调用B:
    • 如果A没开事务:B自己开个新副本(新事务)
    • 如果A开了事务:B直接加入A的副本(共用A的事务)
  • 特点:最常用的,像"团队合作"。
    举例:
  • A(没事务)→ B(REQUIRED):B自己开事务
  • A(有事务)→ B(REQUIRED):B用A的事务

  1. REQUIRES_NEW:必须开新副本,老副本暂停
  • A调用B:
    • 无论A有没有事务,B都会开一个新副本(新事务),A的副本暂停。
  • 特点:B的事务完全独立,成败不影响A。
    举例:
  • A(有事务)→ B(REQUIRES_NEW):A的事务暂停,B开新事务,B完成后A继续。

  1. SUPPORTS:有副本就一起刷,没有就普通执行
  • A调用B:
    • 如果A有事务:B加入A的副本
    • 如果A没事务:B也不开事务,直接裸奔执行
  • 特点:随大流,适合查询方法。
    举例:
  • A(没事务)→ B(SUPPORTS):B不开启事务
  • A(有事务)→ B(SUPPORTS):B用A的事务

  1. NOT_SUPPORTED:拒绝副本,强制普通执行
  • A调用B:
    • 如果A有事务:A的副本暂停,B不用事务执行
    • 如果A没事务:B直接执行
  • 特点:就是不想用事务,比如写日志。
    举例:
  • A(有事务)→ B(NOT_SUPPORTED):A的事务暂停,B无事务执行。

  1. MANDATORY:必须要有副本,否则报错
  • A调用B:
    • 如果A有事务:B加入
    • 如果A没事务:直接报错!
  • 特点:强制要求必须在事务中调用。
    举例:
  • A(没事务)→ B(MANDATORY):抛出异常!

  1. NEVER:禁止副本,有副本就报错
  • A调用B:
    • 如果A有事务:报错!
    • 如果A没事务:B正常执行
  • 特点:就是不想和事务扯上关系。
    举例:
  • A(有事务)→ B(NEVER):抛出异常!

  1. NESTED:嵌套副本(有存档点)
  • A调用B:
    • 如果A有事务:B在A的副本里创建一个"嵌套事务"(像游戏存档点)
      • 如果B失败:回滚到B开始前的状态(读档)
      • A可以继续执行或回滚
    • 如果A没事务:B自己开新事务(等同于REQUIRED)
  • 特点:部分回滚,需要数据库支持(如MySQL的InnoDB)。
    举例:
  • A(有事务)→ B(NESTED):B是A的子事务,B回滚不影响A已执行的操作。

3)Spring事务失效的场景有哪些

(1)事务执行期间出现error、RuntimeException之外的异常,但是并未指定rollbackFor

Spring事务默认只在遇到RuntimeException和Error时才会回滚,如果是其他异常(例如IOException、SQLException),Spring默认不会回滚

public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}

可以通过@TransactionalrollbackFor属性,显式指定需要回滚的异常类型

@Transactional(rollbackFor = Exception.class) // 对所有异常回滚
(2)异常被方法本身捕获,Spring没感知到

使用catch进行捕获之后,Spring无法感知到异常,无法回滚。

(3)同一个类中,方法互相调用

Spring事务是基于动态代理实现的,如果同一个类中的方法A调用方法B,即使方法B有@Transactional,事务也不会生效,因为调用是通过this(目标对象本身)进行的,而不是通过代理对象。

1、失效代码案例:
  • createOrder() 调用 updateOrderStatus() 时,使用的是 this.updateOrderStatus(),而不是代理对象的 updateOrderStatus(),因此 @Transactional 不会生效。
  • 即使 updateOrderStatus() 抛出 RuntimeException,事务不会回滚,因为 Spring 无法拦截这次调用。
@Service
public class OrderService {public void createOrder() {// 调用同类中的事务方法this.updateOrderStatus(); // 事务失效!}@Transactionalpublic void updateOrderStatus() {// 更新订单状态orderDao.updateStatus();// 模拟异常if (true) {throw new RuntimeException("更新订单失败");}}
}
2、解决方案

方案1:注入自身代理(推荐)

@Service
public class OrderService {@Autowired    // 使用 @Autowired 注入自身代理对象(Spring 会处理循环依赖)。private OrderService orderService; // 注入自身代理对象public void createOrder() {// 通过代理对象调用事务方法orderService.updateOrderStatus(); // 事务生效!}@Transactionalpublic void updateOrderStatus() {orderDao.updateStatus();if (true) {throw new RuntimeException("更新订单失败"); // 事务会回滚}}
}

方案2:将事务方法,移到另外一个类,避免自调用问题

(4)方法不是public

Spring源码做了判断,如果不是Public会直接返回。

if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;
}
(5)方法是final或static

考虑动态代理的实现原理,无论是基于JDK还是CGLib,都不允许final和static的修饰


二、SpringBoot

1、SpringBoot的自动装配

csdn文档:
SpringBoot自动装配详解

1)什么是“SpringBoot的自动装配”

  1. 导入依赖,触发默认配置
  • 例如,引入Starter依赖(如 spring-boot-starter-web)后,SpringBoot自动配置相关组件(Tomcat、MVC 等)
  • 例如,引入 spring-boot-starter-jdbc 或相关数据库 Starter 后,只要配置 MySQL 连接信息(url、username、password),SpringBoot就会自动配置基于 HikariCP 的数据源
  1. 约定大于配置
  • 提供合理的默认值(如端口8080)
  • 自动管理依赖版本(jar版本默认与SpringBoot当前版本相对应)

自动装配简单来说,就是自动将第三方的组件的bean装载到IOC容器内,不需要再去写bean相关的配置,符合约定大于配置理念。

2)SpringBoot自动装配的原理

SpringBoot项目的启动类上有一个注解@SpringBootApplication,该注解是对3个注解进行了封装:

  • @SpringBootConfiguration // 标识这是一个 Spring 配置类
  • @ComponentScan // 扫描当前包及其子包的组件(如 @Component, @Service, @Repository)
  • @EnableAutoConfiguration // 启用自动装配 (核心注解)
    其中@EnableAutoConfiguration是实现自动化配置的核心注解。

@EnableAutoConfiguration的作用:
白话:它告诉SpringBoot去扫描,所有预先写好的自动配置类,并根据当前项目的依赖和配置,自动组装需要的Bean,以达到“开箱及用”的效果

SpringBoot自动配置原理 :

  • 1、@EnableAutoConfiguration注解导入AutoConfigurationImportSelector类。
  • 2、执行selectImports方法调用SpringFactoriesLoader.loadFactoryNames()扫描所有jar下面的对应的META-INF/spring.factories文件.
  • 3、限定为@EnableAutoConfiguration对应的value,将这些装配条件的装配到IOC容器中。

2、异常处理

1) SpringBoot异常处理的3种机制

(1) @ExceptionHandler注解(控制器级别/同一个类中)

在Spring Boot中,"控制器"就是指不同的Controller类。简单来说:

  1. @ExceptionHandler注解的作用范围是类级别的,它只能处理定义它的那个类中抛出的异常。
  2. 如果异常是在类A中抛出的,而处理该异常的@ExceptionHandler方法定义在类B中,那么这个处理方法不会生效。
  3. 只有当handleUserNotFound方法和抛出UserNotFoundException异常的代码位于同一个类中时,这个异常处理方法才会被调用。

代码举例:

@RestController
public class UserController {@GetMapping("/users/{id}")public User getUser(@PathVariable Long id) {if (id == 0) {// 这个异常会被handleUserNotFound处理throw new UserNotFoundException("用户不存在"); }return new User(id, "张三");}@ExceptionHandler(UserNotFoundException.class)public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);}
}@RestController
public class ProductController {@GetMapping("/products/{id}")public Product getProduct(@PathVariable Long id) {if (id == 0) {// 这个异常不会被UserController中的方法处理,因为它是在ProductController中抛出的throw new UserNotFoundException("用户不存在");}return new Product(id, "手机");}
}

在这个例子中:

  • 如果访问/users/0,UserController会抛出UserNotFoundException,然后被同一个控制器中的handleUserNotFound方法捕获和处理。
  • 如果访问/products/0,ProductController也抛出了相同类型的UserNotFoundException,但它不会被UserController中的异常处理方法处理,因为异常发生在不同的控制器中。
  • 如果想要处理多个控制器中的相同类型异常,就需要使用@ControllerAdvice或@RestControllerAdvice注解来定义全局异常处理器。
(2)@ControllerAdvice/@RestControllerAdvice(全局级别)

这种方式可以处理所有控制器抛出的异常,是最常用的方式。下面是一个非常简单的全局异常处理示例,使用@RestControllerAdvice(相当于@ControllerAdvice + @ResponseBody的组合):

// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {// 处理所有RuntimeException异常@ExceptionHandler(RuntimeException.class)public String handleRuntimeException(RuntimeException e) {return "发生错误: " + e.getMessage();}// 处理特定异常 - 算术异常@ExceptionHandler(ArithmeticException.class)public String handleArithmeticException(ArithmeticException e) {return "数学计算错误: " + e.getMessage();}}@Controller
@RequestMapping("/userController")  // 在类级别添加这个注解
public class UserController {@GetMapping("/test1")public String test1() {// 会触发RuntimeException处理throw new RuntimeException("这是一个测试异常");}@GetMapping("/test2")public String test2() {// 会触发ArithmeticException处理int result = 10 / 0;return "结果: " + result;}
}

测试结果:
访问:userController/test1,返回结果:发生错误: 这是一个测试异常
访问:userController/test2,返回结果:数学计算错误: / by zero

总结:
更优雅的处理异常

(3)自定义ErrorController(应用级别)

ErrorController 是 Spring Boot 提供的用于处理应用级别错误的接口,当其他异常处理器(如 @ControllerAdvice)没有捕获异常时,最终会由 ErrorController 处理。


3、⭐常用注解

1)启动类注解

@SpringBootApplication

标记主启动类,整合了 @Configuration、@ComponentScan 和 @EnableAutoConfiguration,所有springBoot项目的入口

2)控制器相关

@RestController:

组合 @Controller 和 @ResponseBody,直接返回数据(如 JSON)

  • 场景:编写 RESTful API。
@RestController
public class UserController {@GetMapping("/users")public List<User> getUsers() {return userService.findAll();}
}
@GetMapping:

处理get请求

(1)配合@PathVariable,动态获取url路径中的参数
场景:RESTful 风格接口,通过路径中的变量标识资源。

参数名匹配:

@GetMapping("/users/{id}") // URL示例:/users/1001
public User getUser(@PathVariable Long id) {return userService.findById(id);
}

参数名不一样,则需手动指定:

@GetMapping("/users/{userId}")
public User getUser(@PathVariable("userId") Long id) { ... }

(2)搭配@RequestParam:获取 URL 查询参数

  • 场景:过滤、分页、排序等需要附加参数的 GET 请求。
@GetMapping("/users") // URL示例:/users?page=2&size=10
public List<User> listUsers(@RequestParam(defaultValue = "1") int page, // 默认值@RequestParam(required = false) Integer size // 可选参数
) {return userService.findByPage(page, size == null ? 10 : size);
}
  • 必填性:required = true(默认必填),设为 false 表示可选。
  • 默认值:通过 defaultValue 设置(即使 required = false 也会生效)。

(3)组合使用:路径参数 + 查询参数

  • 场景:更复杂的资源定位,如获取用户订单并过滤状态。
@GetMapping("/users/{id}/orders") // URL示例:/users/1001/orders?status=PAID
public List<Order> getUserOrders(@PathVariable Long id,@RequestParam String status
) {return orderService.findByUserIdAndStatus(id, status);
}
@PostMapping

处理 post请求

(1)搭配 @RequestBody:接收 JSON/XML 请求体

  • 场景:提交结构化数据(如创建用户)。
@PostMapping("/users") // 请求体示例:{"name": "Alice", "age": 25}
public User createUser(@RequestBody User user) {return userService.save(user);
}
  • Content-Type:需为 application/json 或 application/xml。
  • 数据校验:结合 @Valid 校验参数合法性:
@PostMapping("/users")
public User createUser(@Valid @RequestBody User user) { ... }

(2) 搭配 @RequestParam,接收表单数据

  • 场景:传统表单提交(非 RESTful)。
@PostMapping("/login") // Content-Type: application/x-www-form-urlencoded
public String login(@RequestParam String username,@RequestParam String password
) {return authService.login(username, password);
}
  • Content-Type:需为 application/x-www-form-urlencoded。

(3)组合使用:路径参数 + 请求体

  • 场景:部分更新资源,如更新用户的“昵称”和“头像”
@PostMapping("/users/{id}/info") // 更新用户资料
public Profile updateUserInfo(@PathVariable Long id,@RequestBody UserInfo info
) {return "用户ID " + id + " 的昵称更新为:" + info.getNickname() + ",头像更新为:" + info.getAvatar();
}

请求url: POST http://localhost:8080/users/123/info
请求体:

{"nickname": "小明","avatar": "http://xxx.jpg"
}

3)依赖注入

@Autowired
  • 作用:自动注入 Bean,推荐用在构造器上。
  • 场景:注入 Service、Repository 等依赖。

@Component/@Service/@Repository

  • 作用:将类标记为 Spring 管理的组件,分别用于通用组件、业务层、数据层。
  • 场景:分层架构中定义 Bean。
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

4)配置相关

@Configuration 和 @Bean
  • 作用:定义配置类及创建第三方库的 Bean。
  • 场景:配置数据源、线程池等。
@Configuration
public class AppConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
@Value
  • 作用:注入配置文件中的值。
  • 场景:读取 application.properties 中的配置。
@Service
public class MyService {@Value("${app.timeout:30}")private int timeout; // 默认值30
}

5)数据访问

@Entity 和 @Table
  • 作用:定义 JPA 实体类及对应数据库表。
  • 场景:ORM 映射。
@Entity
@Table(name = "users")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;
}

6)参数校验

@Validated 和 @NotBlank
  • 作用:验证请求参数或对象属性。
  • 场景:表单校验、API 参数校验。
@PostMapping("/users")
public User createUser(@Valid @RequestBody User user) {return userService.save(user);
}public class User {@NotBlankprivate String name;
}

7)全局异常处理

@ControllerAdvice 、@ExceptionHandler
  • 作用:全局捕获异常并统一处理。
  • 场景:自定义错误响应。
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(ResourceNotFoundException.class)public ResponseEntity<?> handleNotFound() {return ResponseEntity.notFound().build();}
}

4、JPA数据封装

5、启动流程


三、SpringCloud

1、什么是SpringCloud?

就是“微服务”架构,将一个原本独立的系统,拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过http请求进行通信协作。目前较常见的就是使用阿里的SpringCloudAlibaba

1)Spring Boot 和 Spring Cloud 的区别与联系

SpringCloud是基于springBoot实现的,多个springBoot负责各自的单体服务,是可以独立运行的,然后cloud对其进行管理
例如:
boot_1负责药品字典的管理;boot_2负责每个科室药品的出入库;boot_3负责供应商药品采购;相互boot之间通过http或消息队列进行数据的交互,这些通信能力由SpringCloud组件提供。
boot_2(出入库)需要调用boot_1(药品字典)的接口查询药品信息,通过Feign实现;三者的数据库配置通过Nacos统一管理。

相关文章:

  • 贵族运动项目有哪些·棒球1号位
  • CSS:编写位置分类及优先级
  • 除法未能拿下 一直运行超时
  • javascript<——>进阶
  • linux修改环境变量
  • 【AI平台】n8n入门4:n8n云创建工作流(无须搭建,快速试用14天)
  • 基于arduino的温湿度传感器应用
  • 系统分析师-第十三、十四章
  • 软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(第4版课程精华版)
  • java中线程安全的集合
  • Java中的内部类?
  • 龙虎榜——20250429
  • AXPA17388: 4x45W 车用AB类四通道桥式输出音频功率放大器
  • AimRT 从零到一:官方示例精讲 —— 一、工具链与基本概念
  • windows编译chromium环境配置官方文档说明
  • Mac配置Maven环境变量避坑
  • 【数据治理】数据生命周期
  • 4.28-4.29 Vue
  • MCP 哪家强?深度分析 Cline、Cursor、Trae、Coze 四大平台
  • Astro大屏中关于数据流转的数据接入与数据中心之间的逻辑关系梳理
  • 来论|受美国“保护”,日本民众要付出什么代价?
  • 建设银行南昌分行引金融“活水”,精准灌溉乡村沃土
  • 十四届全国人大常委会第十五次会议在京闭幕
  • 浦发银行一季度净利175.98亿增1.02%,不良率微降
  • 浙商银行外部监事高强无法履职:已被查,曾任建行浙江省分行行长
  • 北京公园使用指南