Skip to content

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-openapi 2.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: redis
java
@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") 注入 ServerEndpointExporter Bean。
  • 核心回调:@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));
    }
}

以「技术栈」为核心的个人知识库