27事务管理AOP
一、MySQL事务回顾
二、Spring事务管理
Spring框架的第一大核心:IOC控制反转
在DeptServiceImpl下删除部门方法下新加一个删除员工信息的操作,注意:此时的id是部门id。
1、问题分析
2、@Transactional-Spring事务管理
一般是在Service实现类的方法上加Transactional注解
执行多次数据访问的增删改操作上需要用到事务
三、Spring事务进阶
1、rollbackFor
Int i = 1/0 是属于运行时异常
这是service层,需要继续往上抛异常
默认情况下只有运行时异常才会进行事务回滚
2、propagation
因为我们需要解散部门时,无论成功还是失败,都要记录操作日志,所以需要用到@Transaction的REQUIRES_NEW的属性,来新建一个事务
pojo/DeptLog
package com.itheima.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {private Integer id;private LocalDateTime createTime;private String description;
}
DeptServiceImple
package com.itheima.service.impl;import com.itheima.mapper.DeptLogMapper;
import com.itheima.mapper.DeptMapper;
import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Dept;
import com.itheima.pojo.DeptLog;
import com.itheima.service.DeptLogService;
import com.itheima.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.util.List;
import java.util.logging.LogManager;@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;// 删除部门id的同时,需要删除与部门id关联的员工信息@Autowiredprivate DeptLogService deptLogService;// 添加日志对象注入// @Autowired
// private DeptLogMapper deptLogMapper;/*1、查询操作* */// 实现方法public List<Dept> list(){List<Dept> deptList = deptMapper.select();return deptList;}/*2、删除指定id* */@Transactional(rollbackFor = Exception.class) // 交给了Spring进行事务管理,将所有异常进行事务处理@Overridepublic void deleteById(Integer id) throws Exception{try {deptMapper.deleteById(id); // 删除部门idint i = 1/0;
// if (true){throw new Exception("出错了...");}empMapper.deleteById(id); // 删除员工信息} finally {// 记录日志描述DeptLog deptLog = new DeptLog();deptLog.setCreateTime(LocalDateTime.now());deptLog.setDescription("执行了解散部门的操作,此次解散的部门id是"+id);deptLogService.insert(deptLog);/***可以不用写DeptLogService和DeptServiceImpl,直接写一个DeptLogMapper然后交给IOC控制器,再在* 这个实现类中注入DeptLogMapper的对象直接调用insert方法,在方法前加入* @Transactional(propagation = Propagation.REQUIRES_NEW)*/
// deptLogMapper.insert(deptLog);}}/*3、新增部门*/public void add(Dept dept){dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.insert(dept);}/*根据ID查询*/public Dept selectById(Integer id){Dept dept = deptMapper.selectById(id);return dept;}@Override/*更新部门名称*/public void update(Dept dept) {dept.setUpdateTime(LocalDateTime.now());dept.setCreateTime(LocalDateTime.now());deptMapper.update(dept);}
}
DpetLogService
package com.itheima.service;import com.itheima.pojo.DeptLog;public interface DeptLogService {void insert(DeptLog deptLog);
}
DeptLogServiceImpl
package com.itheima.service.impl;import com.itheima.mapper.DeptLogMapper;
import com.itheima.pojo.DeptLog;
import com.itheima.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class DeptLogServiceImpl implements DeptLogService {@Autowiredprivate DeptLogMapper deptLogMapper;// 开启一个新事务@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic void insert(DeptLog deptLog) {deptLogMapper.insert(deptLog);}
}
DeptLogMapper
package com.itheima.mapper;import com.itheima.pojo.DeptLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Mapper
public interface DeptLogMapper {@Insert("insert into dept_log(create_time, description) VALUES (#{createTime},#{description})")
// @Transactional(propagation = Propagation.REQUIRES_NEW)public void insert(DeptLog deptLog);
}
四、AOP基础-Spring框架的第二大核心
AOP概述
AOP快速入门
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Slf4j
@Component // 交给IOC容器
@Aspect // 表示这个是AOP类
public class TimeAspect {
// 当前共性功能应该加在哪些方法上@Around("execution(* com.itheima.service.*.*(..))") //切入点表达式public void recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1、获取方法运行开始时间long begin = System.currentTimeMillis();
// 2、运行原始方法,o是原始方法的返回值Object o = joinPoint.proceed();
// 3、获取方法结束运行结束时间long end = System.currentTimeMillis();
// 计算方法耗时
// joinPoint.getSignature():原始方法的签名log.info(joinPoint.getSignature()+"方法耗时:{}ms",end-begin);}
}

小结
五、AOP基础-核心概念
AOP核心概念
AOP执行流程
小结
| 连接点JoinPoint | 能够被AOP所控制的方法 |
|---|---|
| 切入点PointCut | 实际被AOP控制的方法,通过切入点表达式 |
| 通知Advice | 将所有共性功能封装起来的方法 |
| 切面Aspect | 描述通知与切入点之间的对应关系 |
| 目标对象Target | 通知所对应的对象 |
六、AOP进阶
1、通知类型
切入点表达式抽取
小结
2、通知顺序
3、切入点表达式
execution
MyAspect6.java
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Aspect
public class MyAspect6 {
// @Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
// @Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))")//基于接口描述
// @Pointcut("execution(void delete(java.lang.Integer))")包名.类名不建议省略,匹配的范围过大,影响匹配的效率
// @Pointcut("execution(* com.itheima.service.DeptService.*(*))")//匹配返回值为任意,DeptService接口下所有方法任意类型的一个参数
// @Pointcut("execution(* com..DeptService.get(*))")//匹配返回值为任意,com包下任意子包中DeptService接口/类下get方法,为任意类型的一个参数@Pointcut("execution(* com.itheima.service.DeptService.delete(java.lang.Integer)) ||"+"execution(* com.itheima.service.DeptService.list())")//匹配前面一个或者后面任意一个public void pt(){}@Before("pt()")public void before(){System.out.println("before ...6");}
}
@annotation
MyLog
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)//该注解表示注解的生命周期
@Target(ElementType.METHOD)//表示该注解作用在哪一部分
public @interface MyLog {
}
MyAspect7
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
public class MyAspect7 {@Pointcut("@annotation(com.itheima.aop.MyLog)")public void pc(){}@Before("pc()")public void func(){log.info("MyAspect7...");}
}
只需要在想要匹配的方法上加@MyLog注解就可以调用通知方法
小结
4、连接点
MyAspect8
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Slf4j
@Aspect
public class MyAspect8 {@Pointcut("execution(* com.itheima.service.DeptService.*(..))")public void pt(){}@Before("pt()")public void before(JoinPoint joinPoint){log.info("before....");}@Around("pt()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("MyAspect8 around before...");// 1、获取目标类名String className = joinPoint.getTarget().getClass().getName();log.info("目标类名:{}",className);// 2、获取目标方法签名Signature signature = joinPoint.getSignature();log.info("目标方法签名:{}",signature);// 3、获取目标方法名String signatureName = joinPoint.getSignature().getName();log.info("目标方法名:{}",signatureName);// 4、获取目标方法运行参数Object[] args = joinPoint.getArgs();String arg = Arrays.toString(args);log.info("目标方法参数:{}",arg);// 5、执行原始方法,获取返回值Object result = joinPoint.proceed();log.info("目标方法的返回值:{}",result);log.info("MyAspect8 after ...");return result;}@After("pt()")public void after(){log.info("after...");}
}
测试类
package com.itheima;import com.itheima.service.DeptService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootAopQuickstart1ApplicationTests {@AutowiredDeptService deptService;@Testpublic void test1(){deptService.delete(10);}@Testpublic void test2(){deptService.list();}
}
执行delete之后
只有环绕通知才可以执行原始方法
前置通知在原始方法执行前运行,后置通知在原始方法执行后运行
七、AOP案例

1、准备工作
<!--AOP依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.7.1</version>
</dependency>
-- AOP案例
-- 操作日志表
create table operate_log(id int unsigned primary key auto_increment comment 'ID',operate_user int unsigned comment '操作人ID',operate_time datetime comment '操作时间',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_params varchar(1000) comment '方法参数',return_value varchar(2000) comment '返回值',cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
sql表格对应的实体类
package com.itheima.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {private Integer id; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}
2、编码
LogAspect.java
package com.itheima.aop;import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;@Component
@Slf4j
@Aspect //切面类
public class LogAspect {@Autowired//用于获取jwt令牌private HttpServletRequest httpServletRequest;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.itheima.anno.Log)")//切点表达式public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 1、获取操作人ID,当前登录员工id,这一步需要用到jwt令牌,在Header中String jwt = httpServletRequest.getHeader("token");
// 解析jwt令牌Claims claims = JwtUtils.parseJwt(jwt);Integer operateUser = (Integer) claims.get("id");// 2、操作时间LocalDateTime operateTime = LocalDateTime.now();// 3、操作类名String className = joinPoint.getTarget().getClass().getName();// 4、操作方法名String methodName = joinPoint.getSignature().getName();// 5、操作方法参数Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);// 获取方法开始时间long begin = System.currentTimeMillis();
// 6、操作方法返回值,转换为json格式的字符串保存起来Object result = joinPoint.proceed();String returnValue = JSONObject.toJSONString(result);
// 获取结束时间long end = System.currentTimeMillis();// 7、操作耗时long costTime = end - begin;// 将日志内容插入到表中OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);operateLogMapper.insert(operateLog);log.info("AOP操作日志:{}",operateLog);return result;//返回的是执行方法的返回值}
}
OperateLogMapper
package com.itheima.mapper;import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OperateLogMapper {@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")public void insert(OperateLog operateLog);}
注意

相关文章:
27事务管理AOP
一、MySQL事务回顾 二、Spring事务管理 Spring框架的第一大核心:IOC控制反转 在DeptServiceImpl下删除部门方法下新加一个删除员工信息的操作,注意:此时的id是部门id。 1、问题分析 2、Transactional-Spring事务管理 一般是在Service实现类的…...
煤矿电子封条实施方案 yolov7
煤矿电子封条实施方案采用YOLOv7网络模型算法技术,煤矿电子封条实施算法模型过将全国各省矿山实时监测数据,实现对全国各矿山及时有效的处理及分析。YOLOv7 的发展方向与当前主流的实时目标检测器不同,研究团队希望它能够同时支持移动 GPU 和…...
Linux-inode和block概述
操作系统的文件数据除了实际内容之外,通常含有非常多的属性,例如Linux操作系统的文件权限与文件属性。文件系统通常会将这两部分内容分别存放在inode和block中。 inode 和 block 概述 文件是存储在硬盘上的,硬盘的最小存储单位叫做扇区sect…...
安卓开发投屏反控实现方式
在安卓开发中,可以通过MediaProjection API来实现屏幕投屏的功能,同时也可以通过Socket通信实现反控功能。下面将详细介绍实现步骤和注意事项。 1. 创建MediaProjectionManager对象 首先,我们需要创建一个MediaProjectionManager对象&#…...
外网SSH远程连接linux服务器「cpolar内网穿透」
✨个人主页:bit me👇 目 录 视频教程🌴1. Linux CentOS安装cpolar☘️2. 创建TCP隧道🎍3. 随机地址公网远程连接🎋4. 固定TCP地址🎄5. 使用固定公网TCP地址SSH远程 转载自内网穿透工具的文章:无…...
Deferred Components-实现Flutter运行时动态下发Dart代码 | 京东云技术团队
导读 Deferred Components,官方实现的Flutter代码动态下发的方案。本文主要介绍官方方案的实现细节,探索在国内环境下使用Deferred Components,并且实现了最小验证demo。读罢本文,你就可以实现Dart文件级别代码的动态下发。 一、…...
08 集合框架1
什么是数据结构? 存储数据,组织数据的方法,就是对数据做增删改查的操作 常见的数据结构有哪些?各自的优缺点是什么? 数组:擅长修改 查找操作,不擅长增加 删除操作 链表:有单项链表和双向链表,擅长增加和删除操作,不擅长修改和查找的操作 队列:擅长操作头和尾,先进先出,…...
内卷把同事逼成了“扫地僧”,把Git上所有面试题整理成足足24W字测试八股文
互联网大厂更多的是看重学历还是技术? 毫无疑问,是技术,技术水平相近的情况下,肯定学历高/好的会优先一点,这点大家肯定都理解。 说实话,学弟学妹们找工作难,作为面试官招人也难呀!…...
10-jQuery-遍历children、parent、for、each、for...of等
1、for 循环:可以用来遍历数组或类数组对象,但不能用来遍历普通对象。 <ul><li>John</li><li>Doe</li><li>Jane</li><li>Doe</li> </ul><script>var lis $(li);for (var i 0; i &…...
联想集团财报:收入持续下滑,联想集团财务前景已恶化
来源:猛兽财经 作者:猛兽财经 联想集团2023财年第三季度财务业绩回顾 联想集团(00992)于2023年2月16日盘后公布了该公司2023财年第三季度的财报。 财报显示,联想集团的收入已经从2022财年第三季度的201.27亿美元下降到…...
GPT4限制被破解!ChatGPT实现超长文本处理的新方法
目录 前言 使用chat-gpt过程中有哪些痛点 1.无法理解人类情感和主观性 2.上下文丢失 3.约定被打断 那如何去解决这个痛点 Transformer(RMT)怎么去实现的 1.Transformer 模型 2.RMT模型 3.计算推理速率 4.渐进学习能力 总结 写到最后 大家好…...
奋斗,然后成功:我的架构狮之梦
与代码结缘 2018年,当时听说了一个很厉害的人——吴瀚清老师,也就是大家所熟知的“道哥”。关于他的事情有很多传说,于是我也很快成为了他的小迷弟,把吴瀚清老师当成了自己的偶像。 也是那一年,我买了人生中第一本关…...
自定义属性,v-bind computed的使用
0.0 自定义组件的使用 【掌握】 先自定义自己的组件 引入组件 import 组件名 from 路径/文件名 注册组件 <script> export default {components:{ // 组件注册组件名:组件名,组件名1},data(){ // 数据return {}},methods:{ // 方法} } </script&…...
解决城市内涝的措施有哪些?需要用到哪些监测设备?
随着城市化的不断推进,城市内涝问题日益凸显。极端天气事件如暴雨、台风等对城市基础设施和居民生活造成了严重影响。那么,解决城市内涝的措施有哪些?需要用到哪些监测设备?针对上述问题,本文会为大家一一进行讲解。 解决城市内涝的措施有哪…...
Spark大数据处理讲课笔记----Spark任务调度
零、本节学习目标 理解DAG概念了解Stage划分了解RDD在Spark中的运行流程 一、有向无环图 (一)DAG概念 DAG(Directed Acyclic Graph)叫做有向无环图,Spark中的RDD通过一系列的转换算子操作和行动算子操作形成了一个…...
【22-23春】AI作业10-经典卷积网络
1.LeNet & MNIST LeNet是一种神经网络的模型,用于图像识别和分类。他包含 3 个卷积层,2 个池化层,1 个全连接层。其中所有卷积层的所有卷积核都为 5x5,步长 strid1,池化方法都为全局 pooling,激活函数…...
第13章_约束
第13章_约束 1. 约束(constraint)概述 1.1 为什么需要约束 数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的…...
GPC规范--安全域基础概念
概述: 分为三种主流类型: 1、发卡方安全域(Issuer Security Domain, ISD),卡片上首要的、强制性存在的安全域,是卡片管理者(通常是发卡方)在卡片内的代表; 2、补充安全域(Supplementary Security Domain&am…...
C++初阶--C++入门之基础学习
0.前言 C是一门非常好的编程语言,但可能在学习C的过程中会遇到很多困难。人们常说 “一个人走得很快,一群人会走的更远”, 所以就让我们一起攻坚克难,一起征服C吧!从本章开始,我们将开始C的基础学习&#x…...
服务器虚拟化部署
服务器虚拟化部署 1、背景2、目的3、环境4、部署4.1、部署VMware ESXi4.1.1、准备工作4.1.2、部署ESXi4.1.3、配置ESXi4.1.4 、部署虚拟机 1、背景 项目上利旧9台服务器,项目需要使用15台服务器,外购已经没有项目硬件采购预算,只能从目前的…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
