目录
Spring Boot统一功能处理详解(新手完整版)
1. 拦截器详解
1.1 什么是拦截器
1.2 完整代码实现(逐行注释)
1.2.1 定义登录拦截器
1.2.2 注册拦截器到Spring MVC
1.3 拦截器执行流程图解
2. 统一数据返回格式
2.1 为什么需要统一格式?
2.2 完整实现代码
2.2.1 统一结果类Result
2.2.2 全局响应处理器ResponseAdvice
2.3 String类型问题的代码体现
3. 统一异常处理
3.1 为什么需要统一处理?
3.2 完整实现代码
3.2.1 全局异常处理器
3.2.2 自定义业务异常
3.3 异常处理调用链演示
4. 完整项目结构示例
5. 知识点与代码的完整对应关系表
6. 新手最容易踩的坑
6.1 拦截器不生效
6.2 String类型异常
6.3 异常没捕获
7. 总结与最佳实践
7.1 三者的协作流程图
7.2 代码层面的最佳实践
8. 最后的话
这里是为您生成的Markdown文档,内容完全按照您的要求整理,保留了所有代码注释和详细讲解。
Spring Boot Unified Function Handling
Spring Boot统一功能处理详解(新手完整版)
我会整合拦截器、统一返回格式和异常处理三部分内容,提供带逐行注释的完整代码,并详细说明每个知识点如何体现在代码中。
1. 拦截器详解
1.1 什么是拦截器
拦截器是Spring MVC提供的"安检门"机制,能在请求到达Controller之前、之后以及请求完成时插入自定义逻辑。它就像你去商场时要经过的安检:安检前检查包裹(preHandle),安检后刷卡(postHandle),离开时记录时间(afterCompletion)。
核心应用场景:
1.2 完整代码实现(逐行注释)
1.2.1 定义登录拦截器
// import关键字:导入其他包中的类,就像你要用别人的工具得先拿来
// slf4j:Simple Logging Facade for Java,日志门面框架,类似一个日志的"翻译官"
// 它能让你在不改代码的情况下切换log4j、logback等具体实现
import lombok.extern.slf4j.Slf4j;
// Spring框架的组件注解,标记这个类为Spring管理的Bean(就像商品贴上条形码入库)
// Spring容器会自动创建它的实例,其他地方可以直接"借用"
import org.springframework.stereotype.Component;
// Spring MVC的核心接口,实现它就拥有了拦截请求的能力
// 类似"安检员资格证",只有拿到这个证才能在指定位置检查
import org.springframework.web.servlet.HandlerInterceptor;
// Servlet规范提供的HTTP请求对象,封装了客户端发送的所有信息
// 包括请求头、参数、Cookie等,相当于"快递包裹单"
import jakarta.servlet.http.HttpServletRequest;
// Servlet规范提供的HTTP响应对象,用于向客户端返回数据
// 相当于"快递回执单",你可以填写返回内容和状态
import jakarta.servlet.http.HttpServletResponse;
// Session是会话对象,用于在多次请求间保存用户状态
// 就像商场的储物柜,存一次东西,多次取(前提是有钥匙)
import jakarta.servlet.http.HttpSession;
/**
* 登录拦截器
* @Slf4j:Lombok注解,自动生成日志记录器log,不用写LoggerFactory.getLogger()
* @Component:让Spring管理这个拦截器,否则无法注册使用
*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* preHandle:在Controller方法执行前调用(安检门第一道关卡)
* 返回true = 放行(绿灯),返回false = 拦截(红灯)
* * @param request HTTP请求对象(包裹单)
* @param response HTTP响应对象(回执单)
* @param handler 要执行的Controller方法(目标商店)
* @return boolean 是否允许通过
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 记录日志,表示拦截器开始工作
// {}是占位符,实际值会替换到这里,比字符串拼接性能更好
log.info("LoginInterceptor.preHandle() - 开始检查用户登录状态, URI: {}", request.getRequestURI());
// 获取Session,参数false表示"没有就别新建"
// 就像找储物柜钥匙,false表示"找不到就别给我新钥匙"
HttpSession session = request.getSession(false);
// 检查Session是否存在且包含用户信息
// &&是短路与,左边为false右边不执行(避免空指针)
if (session != null && session.getAttribute("user") != null) {
log.info("用户已登录,放行请求");
return true; // 放行,继续执行Controller里的方法
}
// 没登录,设置401状态码(Unauthorized,未授权)
// 就像商场保安说"请出示会员卡"
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // SC_UNAUTHORIZED就是401的常量
log.warn("用户未登录,请求被拦截");
return false; // 拦截,不执行后续操作
}
/**
* postHandle:在Controller方法执行后、视图渲染前调用(第二道关卡)
* 可以修改ModelAndView里的数据或视图名称
* 就像买完东西后,可以在包装袋上加点装饰
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor.postHandle() - Controller执行完毕,准备渲染视图");
// 示例:可以给所有页面统一添加当前用户信息
if (modelAndView != null) {
modelAndView.addObject("currentTime", System.currentTimeMillis());
}
}
/**
* afterCompletion:在整个请求完成后调用(最后关卡)
* 视图已经渲染完毕,客户端已经收到响应
* 通常用于资源清理,比如关闭流、记录最终日志
* 就像顾客离开商场后,保安做收尾工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
log.info("LoginInterceptor.afterCompletion() - 请求处理完成,状态码: {}", response.getStatus());
// 如果有异常,可以在这里记录
if (ex != null) {
log.error("请求处理过程中发生异常", ex);
}
}
}
代码如何体现拦截器特性?
implements HandlerInterceptor:直接实现接口,这是Java的"契约编程",相当于签了合同就必须实现三个方法。
preHandle返回true/false:核心机制,代码中通过if(session...)判断来决定是否放行,这就是"拦截"的本质。
三个方法的执行顺序:通过日志可以观察到,Spring MVC框架保证了先执行preHandle,再执行Controller,然后postHandle,最后afterCompletion。
1.2.2 注册拦截器到Spring MVC
// Spring的依赖注入注解,自动从容器中找LoginInterceptor实例并注入
// 就像你点外卖,@Autowired表示"平台自动分配骑手",你不用自己找
import org.springframework.beans.factory.annotation.Autowired;
// 标记类为配置类,替代传统的XML配置文件
// @Configuration = "这是一个配置文件,Spring启动时要读取"
import org.springframework.context.annotation.Configuration;
// 拦截器注册表,用于添加和管理拦截器
// 就像商场的"安检门管理中心",可以指定哪些门需要安检
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
// Spring MVC配置接口,实现它可以自定义MVC行为(如添加拦截器、资源处理器等)
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 工具类,用于创建不可变列表
import java.util.Arrays;
import java.util.List;
/**
* Web配置类
* @Configuration:告诉Spring"我是个配置类,启动时加载我"
* implements WebMvcConfigurer:表示"我要自定义Spring MVC的行为"
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
// @Autowired:自动注入Spring容器中已经存在的LoginInterceptor对象
// 这里不需要new,Spring会帮我们创建好并注入
@Autowired
private LoginInterceptor loginInterceptor;
// 定义不需要拦截的路径列表
// Arrays.asList():快速创建固定大小的列表
// 就像列个"免检名单",名单上的人不用过安检
private List excludePaths = Arrays.asList(
"/user/login", // 登录接口本身不能拦截,否则无法登录
"/user/register", // 注册接口
"/static/**", // 静态资源(CSS/JS/图片)不用拦截
"/error/**" // 错误页面
);
/**
* addInterceptors:重写父类方法,用于注册拦截器
* Spring MVC启动时会自动调用这个方法
* * @param registry 拦截器注册表(安检门管理中心)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor():添加拦截器
// addPathPatterns("/**"):拦截所有路径(**表示任意层级子路径)
// excludePathPatterns():排除指定路径(白名单)
// 链式调用:像搭积木一样连续配置
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 先全部拦截
.excludePathPatterns(excludePaths); // 再开放部分路径
log.info("拦截器注册完成,已应用登录验证");
}
}
代码如何体现配置灵活性?
/**的匹配规则:代码中通过字符串模式匹配实现,/表示路径分隔符,*通配符表示匹配任意字符。
excludePathPatterns():代码中通过列表排除,实现了"黑名单"机制,这是实际项目中最常用的方式。
@Configuration:Spring的约定,启动时扫描所有标记了此注解的类,自动执行配置方法。
1.3 拦截器执行流程图解
用户请求 → Tomcat → DispatcherServlet → applyPreHandle()
↓ (返回false则中断) ↓ (返回true则继续)
拦截器preHandle() Controller方法执行
↓ ↓
拦截器postHandle() ←─────────── 方法返回
↓
视图渲染
↓
拦截器afterCompletion() ←─────── 请求完成
代码体现:在DispatcherServlet.doDispatch()方法中(Spring源码),三个方法被按顺序调用,这就是结论"拦截器有固定执行顺序"的实现依据。
2. 统一数据返回格式
2.1 为什么需要统一格式?
想象你是前端开发,后端同事张三返回{"name":"张三"},李四返回"李四",王五返回true。你要写三种不同的解析逻辑,维护成本极高!
统一格式后,所有接口都返回:
{
"status": 200,
"data": "实际数据",
"errorMessage": "",
"timestamp": 1234567890
}
前端只需写一套解析逻辑:取data字段即可。
2.2 完整实现代码
2.2.1 统一结果类Result
// Lombok的@Data注解,自动生成getter/setter/toString/equals/hashCode方法
// 相当于让Lombok帮你写样板代码,你只需关注业务字段
import lombok.Data;
/**
* 统一返回结果类(模板)
* @param 泛型,表示data字段可以是任何类型(String、User、List等)
* 就像快递盒,可以装任何东西
*/
@Data
public class Result {
// int:状态码,用数字表示结果(200成功,500失败等)
private int status;
// String:错误信息,失败时告诉用户/前端具体原因
private String errorMessage;
// T:泛型,实际业务数据,成功时存放返回内容
private T data;
// long:时间戳,记录响应的毫秒时间,用于调试和监控
private long timestamp;
/**
* 私有构造方法:防止外部直接new Result()
* 强制使用工厂方法创建,保证统一性
*/
private Result() {
// System.currentTimeMillis():获取当前系统时间的毫秒值
// 从1970年1月1日00:00:00到现在的总毫秒数
this.timestamp = System.currentTimeMillis();
}
/**
* 静态工厂方法:创建成功响应
* static:类方法,无需创建对象直接调用 Result.success()
* :泛型方法,让编译器自动推断T的类型
* * @param data 要返回的业务数据
* @return 包装后的统一结果
*/
public static Result success(T data) {
Result result = new Result<>();
result.setStatus(200); // HTTP状态码200表示成功
result.setData(data); // 将业务数据放入data字段
// errorMessage保持null,表示没有错误
return result;
}
/**
* 静态工厂方法:创建失败响应
*/
public static Result fail(String errorMessage) {
Result result = new Result<>();
result.setStatus(500); // HTTP状态码500表示服务器错误
result.setErrorMessage(errorMessage); // 设置错误详情
// data保持null
return result;
}
/**
* 自定义状态码和数据的响应
* 用于特殊场景,如参数验证失败400,未授权401
*/
public static Result custom(int status, String errorMessage, T data) {
Result result = new Result<>();
result.setStatus(status);
result.setErrorMessage(errorMessage);
result.setData(data);
return result;
}
}
代码如何体现统一性?
私有构造函数:代码中的private Result()强制所有创建必须通过success()/fail()方法,这就是"统一"的制度保障。
泛型<T>:代码中的泛型设计让Result能包装任何类型,这是"通用"的实现手段。
工厂方法:static方法让创建点集中,便于后续添加统一逻辑(如自动填充traceId)。
2.2.2 全局响应处理器ResponseAdvice
// Spring核心类:方法参数描述,包含返回值的类型、注解等信息
// 就像"产品说明书",告诉你这个方法的返回值是什么
import org.springframework.core.MethodParameter;
// HTTP媒体类型,如application/json, text/html
// 决定返回什么格式的数据
import org.springframework.http.MediaType;
// 服务器端HTTP请求抽象,Spring封装后的请求对象
import org.springframework.http.server.ServerHttpRequest;
// 服务器端HTTP响应抽象,Spring封装后的响应对象
import org.springframework.http.server.ServerHttpResponse;
// @ControllerAdvice:控制器通知,对所有的Controller生效
// 类似"广播站",向所有Controller发送统一指令
import org.springframework.web.bind.annotation.ControllerAdvice;
// ResponseBodyAdvice:响应体增强接口,在返回数据写入响应前进行修改
// 就像"包装工",在商品出厂前统一装箱
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
// Jackson库的核心类,用于Java对象和JSON字符串互转
// 就像"翻译官",把Java对象翻译成JSON语言
import com.fasterxml.jackson.databind.ObjectMapper;
// Lombok的@SneakyThrows注解:自动处理受检异常,不用写try-catch
// 简化代码,但不建议在复杂场景使用
import lombok.SneakyThrows;
// Lombok的日志注解
import lombok.extern.slf4j.Slf4j;
/**
* 全局响应处理器
* @ControllerAdvice:这个类会影响所有的Controller返回值
* @Slf4j:记录日志
*/
@Slf4j
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice