多用户跨学科交流系统(3):评论模块与 Spring Boot 全局异常处理
目录
- 🧩评论模块(实体类、接口层、业务层等)
- 🧩全局异常处理
- 🥗修改之前的代码(主要是异常处理部分)
- 1. ResponseEntity是啥?
- 2. 改了哪些内容
- 🥗可深入的点
- 🥗面试问题
- 一、Java / Spring Boot 异常基础
- 二、Spring Boot 异常处理(核心)
- 三、BusinessException(业务异常)
- 四、ResponseEntity
- 五、全局异常处理的写法
- 六、进阶一点的题(你能答得很好)
🌻提前看
本篇博客继续基于前两篇博客,除了完成基础的评论模块,还对异常处理进行了深度解析和改动,使其尽量符合企业级项目。
🧩评论模块(实体类、接口层、业务层等)
基于MyBatis
1. Comment实体类
// src/main/java/com/example/blog/entity/Comment.java
package com.example.blog.entity;import lombok.Data;
import java.time.LocalDateTime;@Datapublic class Comment {private Long id;private Long post_id;private Long user_id;private Long parent_id;private String content;private LocalDateTime created_at;
}
2. CommentMapper (接口层)
// src/main/java/com/example/blog/mapper/CommentMapper.javapackage com.example.blog.mapper;import com.example.blog.entity.Comment;
import org.apache.ibatis.annotations.*;
import java.util.List;@Mapper
public interface CommentMapper {// 插入评论@Insert("""INSERT INTO comment (post_id, user_id, content, created_at)VALUES (#{post_id}, #{user_id}, #{content}, #{created_at})""")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(Comment comment);// 根据文章ID查询评论@Select("SELECT * FROM comment WHERE post_id = #{post_id} ORDER BY created_at DESC")List<Comment> findByPostId(Long post_id);// 删除评论@Delete("DELETE FROM comment WHERE id = #{id}")int deleteById(Long id);
}
3. CommentService(业务层)
// src/main/java/com/example/blog/service/CommentService.javapackage com.example.blog.service;import com.example.blog.entity.Comment;
import com.example.blog.mapper.CommentMapper;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.List;@Service
public class CommentService {private final CommentMapper commentMapper;public CommentService(CommentMapper commentMapper) {this.commentMapper=commentMapper;}public void addComment(Comment comment) {comment.setCreated_at(LocalDateTime.now());commentMapper.insert(comment);}public List<Comment> getCommentsByPostId(Long postId) {return commentMapper.findByPostId(postId);}public void deleteComment(Long id) {commentMapper.deleteById(id);}
}
4. CommentController (接口层)
// src/main/java/com/example/blog/controller/CommentController.javapackage com.example.blog.controller;import com.example.blog.entity.Comment;
import com.example.blog.entity.Result;
import com.example.blog.service.CommentService;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/comments")
public class CommentController {private final CommentService commentService;public CommentController(CommentService commentService){this.commentService=commentService;}//添加评论@PostMappingpublic Result<String> addComment(@RequestBody Comment comment){commentService.addComment(comment);return Result.success("评论发布成功");}//获取某篇文章下的评论@GetMapping("/post/{post_id}")public List<Comment> getComments(@PathVariable Long post_id){return commentService.getCommentsByPostId(post_id);}//删除评论@DeleteMapping("/{id}")public Result<String> deleteComment (@PathVariable Long id) {commentService.deleteComment(id);return Result.success("删除成功");}
}
🧩全局异常处理
1. 创建全局异常处理器
只要你的 Controller / Service 里抛出异常,这里会自动拦截下来并输出统一格式。
// 新建包和类
// com.example.blog.exception.GlobalExceptionHandlerpackage com.example.blog.exception;import com.example.blog.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {//处理所有未捕获异常@ExceptionHandler(Exception.class)public Result<?> handleException(Exception e) {return Result.error("服务器内部错误"+e.getMessage());}// 处理参数非法异常@ExceptionHandler(IllegalArgumentException.class)public Result<?> handleArgument(IllegalArgumentException e) {return Result.error(e.getMessage());}//其他异常类型}
2. 测试一下
在任意 Controller 写一个测试方法:
@GetMapping("/test/error")
public String testError() {throw new IllegalArgumentException("参数不合法!");
}

3. 官方文档+高质量教程
- Spring Framework — Exception handling 部分(官方): https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-rest-exceptions.html
- Spring MVC — @ExceptionHandler / @ControllerAdvice 机制(官方): https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html
- 教程文章:“Get Started with Custom Error Handling in Spring Boot” — 实战角度讲得不错: https://auth0.com/blog/get-started-with-custom-error-handling-in-spring-boot-java/
🥗修改之前的代码(主要是异常处理部分)
我们之前异常返回用的Result,现在改用ResponseEntity
1. ResponseEntity是啥?
一句话:
它是 Spring 用来返回 HTTP 响应的万能容器。
-
你可以用它同时设置:
HTTP 状态码(200、400、404、500……)
响应头 header
响应体 body(你真正要返回的数据) -
不像之前用的 Result<?>,只能返回 body。
-
写法举例:
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Result.error("用户名不存在"));
这样浏览器看到的 HTTP 状态码是 400 Bad Request
更符合 REST 风格,也更利于前端判断。
2. 改了哪些内容
- Result 实体类中 返回错误 函数增加 code 参数。
- 新增BusinessException类
// com/blog/exception/BusinessExceptionpackage com.example.blog.exception;public class BusinessException extends RuntimeException {private int code;public BusinessException(int code,String msg) {super(msg);this.code = code;}public int getCode() {return code;}
}
- 修改异常全局处理器中的内容
package com.example.blog.exception;import com.example.blog.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {// 业务异常@ExceptionHandler(BusinessException.class)public ResponseEntity<Result<?>> handleBusinessException(BusinessException e) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Result.error(e.getCode(), e.getMessage()));}// 参数非法异常@ExceptionHandler(IllegalArgumentException.class)public ResponseEntity<Result<?>> handleIllegalArgument(IllegalArgumentException e) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Result.error(400, "参数不合法:" + e.getMessage()));}// 兜底异常@ExceptionHandler(Exception.class)public ResponseEntity<Result<?>> handleException(Exception e) {e.printStackTrace(); // ⭐ 打印异常栈,方便排查return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Result.error(500, "服务器内部错误"));}
}
- 在业务代码遇到业务错误时抛出这个异常
我主要改了UserService里面的内容,连带UserController返回类型也要改(其他模块的自行去改)
// UserServicepackage com.example.blog.service;import com.example.blog.entity.User;
import com.example.blog.exception.BusinessException;
import com.example.blog.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// 💡 注册:加密密码再存数据库public void register(User user) {//用户名重复if(userMapper.findByUsername(user.getUsername())!=null) {throw new BusinessException(1001,"用户名已存在");}user.setPassword(encoder.encode(user.getPassword()));user.setRole("user");int rows = userMapper.insert(user);if(rows!=1) {throw new BusinessException(1002,"注册失败,请稍后再试");}}// 💡 登录:验证用户名存在 + 密码匹配public User login(String username, String rawPassword) {User dbUser = userMapper.findByUsername(username);if(dbUser == null) {throw new BusinessException(1003,"用户不存在");}if(!encoder.matches(rawPassword,dbUser.getPassword())) {throw new BusinessException(1004,"密码错误");}return dbUser;}
}
//Controller/UserControllerpackage com.example.blog.controller;import com.example.blog.entity.Result;
import com.example.blog.entity.User;
import com.example.blog.service.UserService;
import com.example.blog.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;// 💡 注册接口@PostMapping("/register")public Result<?> register(@RequestBody User user) {userService.register(user);return Result.success("注册成功");}// 💡 登录接口@PostMapping("/login")public Result<?> login(@RequestBody User user) {User dbUser = userService.login(user.getUsername(), user.getPassword());// 登录成功,签发TokenString token = JwtUtil.generateToken(dbUser.getUsername());return Result.success(token);}@GetMapping("/test/error")public String testError() {throw new IllegalArgumentException("参数不合法!");}
}
🥗可深入的点
- Controller 层入参校验(@Valid) + 自动异常处理
- 统一返回体规范化
- 日志系统(Logback)
- 全链路错误码体系
🥗面试问题
我学的这部分内容在面试里主要考察对“异常体系”和“REST API 返回规范”的理解。
重点包括:全局异常处理、异常分类、业务异常设计、ResponseEntity 使用、错误码体系。
这些内容是实习项目中比较“工程化”的能力,和写增删改查的 Controller 拉开差距。
一、Java / Spring Boot 异常基础
1. 异常分为哪两大类?区别是什么?
答案:
- 受检异常(Checked Exception):必须
try/catch或throws,如 IOException、SQLException。- 非受检异常(Unchecked Exception):不强制处理,RuntimeException 及其子类,如 NullPointerException。 区别:编译期是否要求必须处理。
2. RuntimeException 为什么不用强制捕获?
答案: 因为它表示的是程序逻辑错误,不是外部环境问题。 强制捕获反而会让代码臃肿。
3. throw 和 throws 的区别?
答案:
throw:在方法内部抛异常对象throws:声明方法可能抛出某类异常
二、Spring Boot 异常处理(核心)
4. 什么是全局异常处理?为什么要用?
答案: 使用
@RestControllerAdvice + @ExceptionHandler统一捕获项目所有异常。 好处:
- 错误返回格式统一
- 代码更简洁(不用每个 Controller 写 try/catch)
- 便于维护和日志记录
5. @ControllerAdvice 和 @RestControllerAdvice 的区别?
答案:
@ControllerAdvice:返回视图(需要 @ResponseBody 才变成 JSON)@RestControllerAdvice:默认返回 JSON,更适合 REST API
6. @ExceptionHandler 的作用是什么?
答案:
指定方法用来处理某一种(或一类)异常,比如:
@ExceptionHandler(IllegalArgumentException.class)
7. @ExceptionHandler 会按顺序匹配吗?
答案: 不会按代码顺序匹配,而是按异常类型的精确程度。 比如 IllegalArgumentException 会比 Exception 优先命中。
三、BusinessException(业务异常)
8. 业务异常(BusinessException)是用来干嘛的?
答案: 用来处理“业务逻辑相关的错误”,比如:
- 用户不存在
- 密码错误
- 文章已删除
- 权限不足
表现方式:
是正常业务流程里可能出现的可预期错误,而不是程序bug。
9. 为什么要定义 BusinessException,而不是直接用 RuntimeException?
答案:
- 可以带业务错误码
- 前后端沟通更清晰
- 更方便统一处理
- 区分系统错误(500)和业务错误(400,401,403 等)
10. BusinessException 通常包含哪些字段?
答案:
- 错误码(int)
- 错误信息(String)
比如:
throw new BusinessException(1003, "用户不存在");
四、ResponseEntity
11. ResponseEntity 是什么?为什么要用它?
答案: 它是 Spring 用来构建 HTTP 响应的对象,可以同时设置:
- 状态码(如 200/400/500)
- 响应体(body)
- 响应头(headers)
适合严格遵守 REST 风格的项目。
12. ResponseEntity 与直接返回对象的区别?
答案:
| 方式 | 能否设置状态码 | 能否设置 header | 常见场景 |
|---|---|---|---|
| return 对象 | ❌ 不行 | ❌ 不行 | 小项目,简单 JSON API |
| ResponseEntity | ✔ 可以 | ✔ 可以 | 企业、RESTful API |
五、全局异常处理的写法
13. 你写过一个全局异常处理类,它主要处理哪些类型?
答案示例:
- BusinessException(业务异常 → 400)
- IllegalArgumentException(参数非法 → 400)
- Exception(兜底异常 → 500)
并返回统一格式的
Result。
14. 为什么兜底异常要打印 e.printStackTrace?
答案: 因为兜底异常通常是系统 bug,不打印日志会导致你无法排查问题。
15. 实际开发中,IllegalArgumentException 什么时候会出现?
答案示例:
- 手动检查参数
- 对象为空
- 传入非法数值
- 自己
throw new IllegalArgumentException("xxx")
六、进阶一点的题(你能答得很好)
16. 你如何设计一套错误码体系?
简单答案(面试官会觉得你理解了):
系统级错误与业务级错误分开管理,例如:
- 5000~5999:系统异常
- 1000~1999:用户模块错误
- 2000~2999:文章模块错误
- 3000~3999:评论模块错误
并在 BusinessException 中使用统一的字段返回。
17. 为什么全局异常类里面不要大量捕获具体异常?
答案:
- 太细会使代码难维护
- 优先只处理常见的(业务、参数、系统)即可
- 其他异常可以在日志系统中追踪
