外观
SpringBoot-2 学习笔记
SpringBoot-1 的进阶篇,记录 AOP、Maven 多模块、Swagger、Redis、SpringTask、WebSocket、POI 七个进阶主题的复习要点。
1. SpringAop切面编程思想
【重点要点】
- AOP(面向切面编程):把"横切逻辑"(日志、权限、事务、统计耗时)从业务代码中抽出来,通过切面统一织入,业务方法保持纯净。
- 核心名词:
@Aspect切面类、@Pointcut切入点(匹配哪些方法)、@Before/@After/@AfterReturning/@AfterThrowing/@Around通知、JoinPoint连接点。 - 常用切入点表达式:
execution(public * com.xx.service.*.*(..))匹配指定包所有方法;@annotation(xxx)按注解匹配。 @Around最灵活:ProceedingJoinPoint.proceed()调用原方法,前后可加逻辑、可改返回值、可捕获异常。- 引入依赖:
spring-boot-starter-aop;AOP 基于动态代理,同类内部调用(this.xxx())不走代理,切面失效。 - 易错点:切面类要加
@Component或@Aspect+ 组件扫描;环绕通知必须return pjp.proceed();切入点写错导致"切不到"或"误切"。
java
@Aspect
@Component
public class LogAspect {
// 匹配 controller 包下所有 public 方法
@Pointcut("execution(public * com.xx.controller..*.*(..))")
public void controllerPointcut() { }
@Around("controllerPointcut()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 调用原方法
long cost = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " 耗时 " + cost + "ms");
return result;
}
}2. Maven分模块开发
【重点要点】
- 多模块目的:按业务/功能拆分工程(common 公共、domain 实体、dao、service、web),各模块独立编译、依赖清晰、便于复用。
- 父工程:
<packaging>pom</packaging>,用<modules>聚合子模块、<dependencyManagement>统一管理依赖版本。 - 子模块:
<parent>指向父工程,依赖父管理过的坐标时不用写版本号。 - 模块间依赖:子模块通过
<dependency>引用其他模块(如 web 依赖 service、service 依赖 dao),Maven 按依赖顺序构建 install。 - 启动类放在 web(最外层)模块;公共代码放 common 模块,被各模块引用。
- 易错点:父 pom 忘写
<modules>导致子模块不参与构建;循环依赖(A 依赖 B、B 依赖 A)直接报错,需用 api 模块消除;install 才能让其他模块引用到本地新版本。
xml
<!-- 父 pom.xml -->
<groupId>com.example</groupId>
<artifactId>blog-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>blog-common</module>
<module>blog-system</module>
<module>blog-web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>blog-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>xml
<!-- 子模块 blog-web/pom.xml -->
<parent>
<groupId>com.example</groupId>
<artifactId>blog-parent</artifactId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>blog-common</artifactId>
</dependency>
</dependencies>3. Swagger接口测试
【重点要点】
- Swagger 自动生成接口文档:后端加注解描述接口,生成可在线调试的 UI(knife4j 是国产增强版,访问
/doc.html)。 - 核心注解:
@Api(类)/@ApiOperation(方法)/@ApiParam(参数)/@ApiModel+@ApiModelProperty(实体字段说明)。 - Spring Boot 3 用
springdoc-openapi2.x(jakarta),注解换@Tag/@Operation/@Parameter/@Schema。 - 生产环境关闭:
knife4j.enable=false或按 profile 关闭,避免接口信息泄露。 - 易错点:被 Spring Security 拦截时需放行
/doc.html、/v3/api-docs(或/v2/api-docs)等路径;实体不加注解时文档字段说明为空。
xml
<!-- Spring Boot 2.x:swagger 注解版 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>java
@Api(tags = "用户管理")
@RestController
@RequestMapping("/user")
public class UserController {
@ApiOperation("根据 id 查询用户")
@GetMapping("/{id}")
public Result<User> get(@ApiParam("用户 id") @PathVariable Long id) {
return Result.success(userService.getById(id));
}
}java
@ApiModel("用户实体")
@Data
public class User {
@ApiModelProperty("用户 id")
private Long id;
@ApiModelProperty("用户名")
private String name;
}4. Redis入门
【重点要点】
- Redis:基于内存的高性能键值数据库,常用作缓存、分布式锁、计数器、会话共享。单线程执行命令,原子性有保障。
- 五种类型与 Spring 操作对应:
opsForValue()(String)、opsForHash()、opsForList()、opsForSet()、opsForZSet()。 - 集成:
spring-boot-starter-data-redis+spring-boot-starter-cache;@Cacheable/@CacheEvict/@CachePut注解实现方法级缓存。 - 缓存失效三策略:
@Cacheable(cacheNames, key = "#id")、@CacheEvict(beforeInvocation)更新/删除时清缓存。 - 序列化:
RedisTemplate默认 JDK 序列化易乱码,key 用StringRedisSerializer,value 用 JSON 序列化。 - 易错点:缓存 key 设计要有业务前缀;更新数据库与清缓存顺序不当易出现脏读(先更新库再清缓存);Redis 宕机要能降级(try-catch 走数据库)。
yaml
spring:
redis:
host: localhost
port: 6379
cache:
type: redisjava
@Service
public class UserServiceImpl implements UserService {
@Cacheable(cacheNames = "user", key = "#id")
@Override
public User getById(Long id) {
return userMapper.selectById(id); // 命中缓存则不走这里
}
@CacheEvict(cacheNames = "user", key = "#id")
@Override
public void update(Long id, User user) {
userMapper.updateById(user); // 更新后清除缓存
}
}5. SpringTask
【重点要点】
- Spring Task 是 Spring 内置定时任务框架,
@EnableScheduling开启,@Scheduled标注方法。 - 三种触发:
cron(表达式)、fixedRate(固定频率,按开始时间,任务超时会重入)、fixedDelay(结束后延时,不重入)。 - cron 6 段:秒 分 时 日 月 周(无年);周字段 1=周日;日/周同限定时是"或"关系。
- 单线程调度是默认,多任务长任务要配线程池(
spring.task.scheduling.pool.size)。 - 动态 cron 用
SchedulingConfigurer+CronTrigger,改配置不重启生效。 - 易错点:任务方法无参 void、所在类必须是 Bean;长任务加防重入(
fixedRate可能导致堆积);异常要捕获避免影响调度。
java
@Component
public class CleanTask {
// 每天凌晨 2 点执行
@Scheduled(cron = "0 0 2 * * ?")
public void cleanExpired() {
System.out.println("清理过期数据:" + LocalDateTime.now());
}
// 每次执行结束后 10 分钟
@Scheduled(fixedDelay = 600_000)
public void syncCache() {
System.out.println("同步缓存:" + LocalDateTime.now());
}
}6. WebSocket网络协议链接,互发消息
【重点要点】
- WebSocket:全双工长连接协议,服务端可主动推送消息,适合聊天、在线通知、实时数据(区别于 HTTP 的请求-响应)。
- Spring Boot 集成:
spring-boot-starter-websocket+@ServerEndpoint("/ws")注入ServerEndpointExporterBean。 - 核心回调:
@OnOpen(建立连接)、@OnMessage(收到消息)、@OnClose(关闭)、@OnError(异常)。 - 关键管理:用
ConcurrentHashMap<String, Session>保存在线会话,发消息session.getBasicRemote().sendText(...)。 - 易错点:WebSocket 不走 Spring 的 Controller 拦截器,
@Autowired类成员可能为 null(用静态注入或从容器取 Bean);多实例部署需配合消息中间件(如 Redis 发布订阅)做消息转发。
java
@Component
@ServerEndpoint("/ws")
public class ChatEndpoint {
private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
SESSIONS.put(session.getId(), session);
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
// 广播给所有在线会话
for (Session s : SESSIONS.values()) {
s.getBasicRemote().sendText(session.getId() + ": " + message);
}
}
@OnClose
public void onClose(Session session) {
SESSIONS.remove(session.getId());
}
}java
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); // 注册 @ServerEndpoint
}
}7. ApachePOI技术操作Excel文件
【重点要点】
- Apache POI:操作 Office 文件的 Java 库;Excel 相关:HSSF(.xls 老格式)、XSSF(.xlsx 新格式)、SXSSF(流式大数据写,常用)。
- 写流程:
new XSSFWorkbook()→createSheet→createRow(i)→createCell(j).setCellValue()→workbook.write(outputStream)。 - 读流程:
WorkbookFactory.create(inputStream)→ 遍历 Sheet/Row/Cell →cell.getCellType()判断类型再取值。 - 样式:
CellStyle+Font设置居中、加粗、边框;合并单元格sheet.addMergedRegion(new CellRangeAddress(...))。 - 大数据量导出用 SXSSFWorkbook(滑动窗口,内存友好);导入用事件驱动
XSSFReader/EasyExcel 更优。 - 易错点:最后必须
workbook.close()/fileOut.close()释放资源;日期单元格用cell.getDateCellValue();读数字判断cellType == NUMERIC && DateUtil.isCellDateFormatted(cell)。
java
// 导出 Excel
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = wb.createSheet("用户列表");
Row head = sheet.createRow(0);
head.createCell(0).setCellValue("ID");
head.createCell(1).setCellValue("姓名");
int rowIdx = 1;
for (User u : userList) {
Row row = sheet.createRow(rowIdx++);
row.createCell(0).setCellValue(u.getId());
row.createCell(1).setCellValue(u.getName());
}
wb.write(out);
// out.toByteArray() 作为文件下载响应体
}java
// 导入 Excel
try (Workbook wb = WorkbookFactory.create(inputStream)) {
Sheet sheet = wb.getSheetAt(0);
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
long id = (long) row.getCell(0).getNumericCellValue();
String name = row.getCell(1).getStringCellValue();
users.add(new User(id, name));
}
}