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)为什么推荐“构造器”的注入方式?
- 不可变性(final支持):被final关键字修饰,不可变
- 提前暴露循环依赖问题:注解注入,存在“循环依赖”问题(A依赖B,B依赖A),使用构造器注入,在项目启动时,会立刻抛出异常(BeanCurrentlyInCreationException)
- 避免空指针异常(NPE):避免“空指针异常”,启动项目的时候,例如dao层没有设置@Mapper注解被注入到容器中,会立马抛出异常
- 显式声明依赖关系:通过构造器,一目了然看出所有依赖
- 更好的可测试性:无需Spring容器,直接通过new创建对象进行单元测试
(2)@Autowired、@Resource、@Inject等注解注入,有何区别?
- @Autowired是Spring自带的,只支持Spring容器,@Resource是JSR250(jdk)规范实现的,可以脱离Spring,@Inject是JSR330规范实现的
- @Autowired、@Inject用法基本一样,不同的是@Inject没有required属性
- @Autowired、@Inject是默认按照类型(byType)匹配的,@Resource是按照名称(byName)匹配的
- @Autowired 按照名称匹配需要和@Qualifier一起使用;@Inject需配合@Named一起使用;@Resource则通过name进行指定
- @Autowired可以修饰构造器,@Resource不行
- 如果某个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动态代理的区别
- 实现原理:
- jdk:基于Java反射机制,通过实现InvocationHandler接口拦截方法调用
- cglib:通过生成目标类的子类实现代理
- 适用范围:
- jdk:只能代理实现了接口的类
- cglib:可以代理未实现任何接口的类(除了final类、static、final方法)
- 方法调用机制:
- jdk:通过反射机制调用目标方法
- cglib:通过FastClass机制直接调用目标方法,避免了反射调用
(3)为什么SpringBoot2.0之后,默认是cglib的动态代理?
- 更通用的代理机制:CGLIB可以代理没有实现接口的类,而JDK动态代理只能代理实现了接口的类
- cglib是实现目标类的子类,但是final修饰的类无法被继承,static修饰的方法无法被重写
- 简化开发体验:开发人员不需要刻意设计接口和实现类的结构来支持AOP,可以更专注于业务逻辑。
- 跨JDK版本的一致性:cglib是一个第三方的jar包,不会因为jdk版本的不同而有显著的变化
- 性能考虑:虽然早期JDK动态代理性能不如CGLIB,但这个差距在现代JDK版本中已经缩小。不过,CGLIB在大量方法调用场景下仍可能有一定优势,因为它避免了反射调用的开销。
- 在被使用到时候,生成对应的子类,但是避免了反射,调用起来会很快
4)Spring AOP 与 AspectJ(了解即可)
是2种不同的AOP框架:
特性 | SpringAOP | AspectJ |
---|---|---|
实现方式 | 基于动态代理(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(默认):
- Spring默认给每个Bean定义一个共享实例(类似全局镜像)。
a. 例如:@Autowired User user_2 代码执行十次,拿到的都是同一个对象,除非用@Scope(“prototype”)改为多例 - 单例在高并发是不安全的,如果想安全,可以使用ThreadLocal
多例Bean:
- @Scope(“prototype”),每次获取(如@Autowired)时,Spring都会创建一个全新的实例,就像你每次new一个对象一样。
- 适用场景:
a. 计数器(Counter):如果计数器是单例的,所有用户会共享同一个计数,导致数据混乱
b. 短生命周期的对象(临时对象),用完即销毁
3)Bean的生命周期
CSDN文档(5、7、10步剖析Bean的生命周期):
Bean的生命周期(五步、七步、十步法剖析)
(1)什么是Bean的生命周期?为什么要知道?
对象从创建开始到最终销毁的整个过程;
可以在生命周期的某个节点,执行特点的业务逻辑
(2)5步分析法
- 第一步:实例化Bean(调用无参构造器)
- 第二步:Bean属性赋值(调用set方法)
- 第三步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
- 第四步:使用Bean
- 第五步:销毁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步,无参构造器,实例化Bean
第2步,setter方法给对象属性赋值
第3步,初始化Bean
第4步,使用Bean - BeanUser{userName='张三(Value注解默认设置用户名)'}
第5步:销毁Bean
** 注意:**
- 只有正常关闭spring容器(调用close方法),bean的销毁方法才会被调用
- @PostConstruct 注解指定“初始化方法”,@PreDestroy 注解指定“销毁方法”
(3)7步分析法
在“5步分析法”中,第3步是初始化Bean,如果还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”;需要编写一个类实现BeanPostproccessor接口,并重写里面的befor和after方法。
就能将Bean的生命周期分为7步:
- 第1步:实例化Bean(调用无参构造器)
- 第2步:Bean属性赋值(调用set方法)
- 第3步:执行“Bean后处理器”的before方法
- 第4步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
- 第5步:执行“Bean后处理器”的after方法
- 第6步:使用Bean
- 第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步,无参构造器,实例化Bean
第2步,setter方法给对象属性赋值
Bean后处理器的before方法执行,即将开始初始化
第3步,初始化Bean
Bean后处理器的after方法执行,已完成初始化
第4步,使用Bean - BeanUser{userName='张三(Value注解默认设置用户名)'}
第5步:销毁Bean
(4)10步分析法
比上面的“7步分析法”多了哪3步:
- 在“Bean后处理器”before方法之前干了什么事?
- 检查Bean是否实现了Aware相关的接口,如果实现了接口则调用这些接口中的方法;调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。
- 在“Bean后处理器”before方法之后干了什么事?
- 检查Bean是否实现了InitializingBean接口,如果实现了,则调用接口中的方法。
- 使用Bean之后,或者说销毁Bean之前干了什么事?
- 检查Bean是否实现了DisposableBean接口,如果实现了,则调用接口中的方法
**总结:**添加的这3个点位的特点,都是在检查你这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法!
**Aware相关的接口包括:**BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
- 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
- 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
- 当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步,无参构造器,实例化Bean
第2步,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步:实例化Bean(调用无参构造器)
- 第2步:Bean属性赋值(调用set方法)
- 第3步:“Bean后处理器”的before方法之前,做了什么
- 第4步:执行“Bean后处理器”的before方法
- 第5步:“Bean后处理器”的before方法之后,做了什么
- 第6步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写)
- 第7步:执行“Bean后处理器”的after方法
- 第8步:使用Bean
- 第9步:销毁Bean之前,做了什么
- 第10步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写)
4)三级缓存,解决循环依赖问题
- Spring只是解决了单例模式下属性循环依赖问题
- A依赖B,B又依赖A:创建A → 需要注入B → 创建B → B需要注入A → A还未创建完成…
- Spring为了解决单例的循环依赖问题,使用了三级缓存
(1)三级缓存
bean的诞生,从三级 -> 二级 -> 一级
- 三级缓存(singletonFactories):Bean工厂,储存new出来的原始对象
a. 就是自己的对象,没有ioc、代理这些东西 - 二级缓存(earlySingletonObjects):存储半成品Bean,这些Bean已创建但还未完全初始化,用于提前暴露创建中的Bean
a. 提前暴露创建中的Bean:指的就是已经创建出对象实例(即已经调用了构造方法完成对象实例化),但尚未完全 完成依赖注入(属性赋值)和初始化(如调用initMethod、afterPropertiesSet等)的Bean。 - 一级缓存(singletonObjects):存储完全初始化好的单例Bean,这些Bean已经完成了属性注入和初始化方法调用
1、什么是“提前暴露创建中的Bean”?
具体来说,Bean的创建过程大致分为三个阶段:
- 实例化:调用构造方法创建对象实例
- 属性赋值:注入依赖的其他Bean或属性
- 初始化:执行初始化方法等
提前暴露创建中的Bean:就是处于“二级缓存”中,但是 属性赋值(依赖注入)未完全完成
(2)三级缓存,如何解决“循环依赖”问题
假设A和B互相依赖,先创建A:
- 创建A的空对象
- 把A的工厂放入三级缓存
- 给A填充属性,发现需要B
- 去创建B对象
- 把B的工厂放入三级缓存
- 给B填充属性,发现需要A
- 关键步骤:从三级缓存获取A的工厂,创建早期版本的A,并把A升级到二级缓存
- 用二级缓存中的A对象注入给B
- B完成初始化,放入一级缓存
- 继续A的属性填充,注入已完成的B
- A完成初始化,放入一级缓存
总结:
Spring先暴露空壳对象A到三级缓存,让B能引用到它完成初始化,再用完整的B去完成A的初始化,从而打破循环依赖的死锁
(3)Spring为什么不能解决单例之外的循环依赖
- 原理冲突:三级缓存依赖于Bean的复用性,而非单例Bean(如prototype作用域)每次请求都会创建新实例,无法复用缓存中的对象
- 生命周期不同:非单例Bean的生命周期由使用者管理,Spring不会缓存它们,每次获取都是新创建的对象
- 资源考量:缓存所有的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)事务的传播行为
- REQUIRED(默认):有副本就一起刷,没有就开新副本
- A调用B:
- 如果A没开事务:B自己开个新副本(新事务)
- 如果A开了事务:B直接加入A的副本(共用A的事务)
- 特点:最常用的,像"团队合作"。
举例: - A(没事务)→ B(REQUIRED):B自己开事务
- A(有事务)→ B(REQUIRED):B用A的事务
- REQUIRES_NEW:必须开新副本,老副本暂停
- A调用B:
- 无论A有没有事务,B都会开一个新副本(新事务),A的副本暂停。
- 特点:B的事务完全独立,成败不影响A。
举例: - A(有事务)→ B(REQUIRES_NEW):A的事务暂停,B开新事务,B完成后A继续。
- SUPPORTS:有副本就一起刷,没有就普通执行
- A调用B:
- 如果A有事务:B加入A的副本
- 如果A没事务:B也不开事务,直接裸奔执行
- 特点:随大流,适合查询方法。
举例: - A(没事务)→ B(SUPPORTS):B不开启事务
- A(有事务)→ B(SUPPORTS):B用A的事务
- NOT_SUPPORTED:拒绝副本,强制普通执行
- A调用B:
- 如果A有事务:A的副本暂停,B不用事务执行
- 如果A没事务:B直接执行
- 特点:就是不想用事务,比如写日志。
举例: - A(有事务)→ B(NOT_SUPPORTED):A的事务暂停,B无事务执行。
- MANDATORY:必须要有副本,否则报错
- A调用B:
- 如果A有事务:B加入
- 如果A没事务:直接报错!
- 特点:强制要求必须在事务中调用。
举例: - A(没事务)→ B(MANDATORY):抛出异常!
- NEVER:禁止副本,有副本就报错
- A调用B:
- 如果A有事务:报错!
- 如果A没事务:B正常执行
- 特点:就是不想和事务扯上关系。
举例: - A(有事务)→ B(NEVER):抛出异常!
- NESTED:嵌套副本(有存档点)
- A调用B:
- 如果A有事务:B在A的副本里创建一个"嵌套事务"(像游戏存档点)
- 如果B失败:回滚到B开始前的状态(读档)
- A可以继续执行或回滚
- 如果A没事务:B自己开新事务(等同于REQUIRED)
- 如果A有事务:B在A的副本里创建一个"嵌套事务"(像游戏存档点)
- 特点:部分回滚,需要数据库支持(如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);
}
可以通过@Transactional
的rollbackFor
属性,显式指定需要回滚的异常类型
@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的自动装配”
- 导入依赖,触发默认配置
- 例如,引入Starter依赖(如 spring-boot-starter-web)后,SpringBoot自动配置相关组件(Tomcat、MVC 等)
- 例如,引入 spring-boot-starter-jdbc 或相关数据库 Starter 后,只要配置 MySQL 连接信息(url、username、password),SpringBoot就会自动配置基于 HikariCP 的数据源
- 约定大于配置
- 提供合理的默认值(如端口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类。简单来说:
- @ExceptionHandler注解的作用范围是类级别的,它只能处理定义它的那个类中抛出的异常。
- 如果异常是在类A中抛出的,而处理该异常的@ExceptionHandler方法定义在类B中,那么这个处理方法不会生效。
- 只有当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统一管理。