在 SpringBoot 开发中,统一功能处理是提升代码复用性、降低维护成本的关键。本文将围绕拦截器、统一数据返回格式、统一异常处理三大核心功能,结合实战案例与源码解析,带你掌握 SpringBoot 统一功能处理的实现方案。
一、拦截器:请求的 "守门人"
在传统开发中,若需对多个接口做登录校验,需在每个接口重复编写 Session 判断逻辑,冗余且难维护。拦截器作为 Spring 框架核心功能,可统一拦截请求,在目标方法执行前后嵌入自定义逻辑,完美解决此类问题。
1.1 拦截器核心概念
拦截器是 Spring MVC 提供的请求拦截机制,能在请求到达 Controller 前、Controller 执行后、视图渲染完成后执行自定义逻辑,主要用于:
- 登录状态校验
- 接口访问权限控制
- 请求参数预处理 / 日志记录
1.2 拦截器使用步骤
拦截器的实现需两步:定义拦截器 + 注册配置拦截器。
步骤 1:定义拦截器(实现 HandlerInterceptor 接口)
需重写三个核心方法,分别对应不同执行时机:
@Slf4j
@Component // 注入Spring容器
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行前执行(核心:请求拦截逻辑)
* @return true:放行;false:拦截(后续逻辑不执行)
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor:目标方法执行前执行");
// 登录校验逻辑:从Session获取用户信息
HttpSession session = request.getSession(false); // 不自动创建Session
if (session != null && session.getAttribute("SESSION_USER_KEY") != null) {
return true; // 已登录,放行
}
// 未登录,返回401(Unauthorized)
response.setStatus(401);
return false;
}
/** 目标方法执行后执行(视图渲染前,后端开发较少用) */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor:目标方法执行后执行");
}
/** 视图渲染完毕后执行(最后执行,可做资源清理) */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor:视图渲染完毕后执行");
}
}
步骤 2:注册配置拦截器(实现 WebMvcConfigurer)
通过 addPathPatterns
指定拦截路径,excludePathPatterns
指定排除路径(如登录接口、静态资源):
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns( // 排除无需拦截的路径
"/user/login", // 登录接口
"/**/*.js", "/**/*.css", // 静态资源(JS/CSS)
"/**/*.png", "/**/*.html" // 静态资源(图片/HTML)
);
}
}
拦截路径规则
不同路径表达式对应不同拦截范围,具体如下:
拦截路径 | 含义 | 示例 |
---|---|---|
/* | 一级路径 | 匹配 /user、/book,不匹配 /user/login |
/** | 任意级路径 | 匹配 /user、/user/login、/book/add |
/book/* | /book 下一级路径 | 匹配 /book/add,不匹配 /book/add/1 |
/book/** | /book 下任意级路径 | 匹配 /book、/book/add、/book/add/2 |
1.3 拦截器执行流程
结合 Spring MVC 核心组件 DispatcherServlet
,拦截器的执行流程如下:
- 请求先进入
DispatcherServlet
,由其调用getHandler
获取请求对应的拦截器链; - 执行拦截器链的
preHandle
方法:若返回true
,继续执行下一个拦截器;若返回false
,中断请求; - 所有
preHandle
放行后,执行 Controller 目标方法; - 目标方法执行完成后,反向执行拦截器链的
postHandle
方法; - 视图渲染完成(后端开发几乎不涉及),反向执行
afterCompletion
方法; - 最终通过
DispatcherServlet
向客户端返回响应。
1.4 源码解析:DispatcherServlet 核心作用
DispatcherServlet
是 Spring MVC 的 "中央调度器",所有请求都需经过它处理。其核心逻辑在 doDispatch
方法中,关键步骤包括:
- 获取执行链 :通过
getHandler
找到请求对应的拦截器链(HandlerExecutionChain
); - 获取适配器 :通过
getHandlerAdapter
找到适配 Controller 的HandlerAdapter
(适配模式应用); - 执行拦截器 preHandle :调用
mappedHandler.applyPreHandle
,遍历执行所有拦截器的preHandle
; - 执行目标方法 :通过
handlerAdapter.handle
调用 Controller 方法; - 执行拦截器 postHandle 与 afterCompletion:目标方法执行后,反向执行后续拦截器逻辑。
二、统一数据返回格式:前后端协作的 "语言规范"
前后端分离项目中,若每个接口返回格式不一致(如有的返回实体类、有的返回 String、有的返回 Boolean),会增加前端解析成本。统一数据返回格式可解决此问题,让所有接口返回结构一致。
2.1 实现方案:@ControllerAdvice + ResponseBodyAdvice
通过 @ControllerAdvice
(控制器通知类)结合 ResponseBodyAdvice
(响应体增强接口),可在响应返回前统一封装数据。
步骤 1:定义统一返回实体类
先定义一个通用的返回结构 Result
,包含状态码、错误信息、数据体:
@Data
public class Result<T> {
private String status; // 状态:SUCCESS/FAIL
private String errorMessage; // 错误信息(失败时非空)
private T data; // 数据体(成功时非空)
// 成功响应静态方法
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setStatus("SUCCESS");
result.setData(data);
return result;
}
// 失败响应静态方法
public static <T> Result<T> fail(String errorMessage) {
Result<T> result = new Result<>();
result.setStatus("FAIL");
result.setErrorMessage(errorMessage);
result.setData(null);
return result;
}
}
步骤 2:实现 ResponseBodyAdvice 统一封装
通过 ResponseBodyAdvice
的 beforeBodyWrite
方法,在响应返回前对数据进行封装:
@Slf4j
@ControllerAdvice // 全局控制器通知
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
private static final ObjectMapper MAPPER = new ObjectMapper(); // Jackson序列化工具
/**
* 是否对响应进行处理(true:处理;false:跳过)
*/
@Override
public boolean supports(MethodParameter returnType, Class<?> converterType) {
return true; // 对所有响应生效
}
/**
* 响应返回前执行:统一封装为Result格式
*/
@SneakyThrows // 简化异常处理
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<?> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 1. 若已为Result类型,直接返回(避免重复封装)
if (body instanceof Result) {
return body;
}
// 2. 若为String类型,需手动序列化(解决StringHttpMessageConverter类型转换问题)
if (body instanceof String) {
return MAPPER.writeValueAsString(Result.success(body));
}
// 3. 其他类型,统一封装为成功响应
return Result.success(body);
}
}
2.2 关键问题:String 类型响应的特殊处理
若不处理 String 类型,会出现 ClassCastException: Result cannot be cast to String
异常。原因是:
- Spring MVC 默认注册了
StringHttpMessageConverter
(处理 String 响应)和MappingJackson2HttpMessageConverter
(处理 JSON 响应); - 当返回 String 时,Spring 优先使用
StringHttpMessageConverter
,但该转换器无法将Result
类型(封装后的数据)转为 String; - 解决方案:手动使用 Jackson 将
Result
序列化为 String,避免类型转换异常。
2.3 统一返回的优势
- 降低沟通成本:前后端无需针对每个接口协商返回格式;
- 简化前端解析 :前端可通过固定字段(如
status
判断成功 / 失败,data
获取数据)处理所有接口; - 便于维护 :若需修改返回结构,只需调整
Result
类和ResponseAdvice
,无需修改所有接口。
三、统一异常处理:优雅应对程序 "意外"
程序运行中难免出现异常(如空指针、除零异常、自定义业务异常),若未统一处理,会返回默认的 500 错误页面或杂乱的异常信息。统一异常处理可捕获所有异常,返回结构化的错误响应。
3.1 实现方案:@ControllerAdvice + @ExceptionHandler
@ExceptionHandler
是异常处理器注解,结合 @ControllerAdvice
可全局捕获异常,并返回统一格式的错误响应。
步骤 1:全局异常处理器实现
@Slf4j
@ControllerAdvice // 全局生效
@ResponseBody // 确保返回JSON格式
public class GlobalExceptionHandler {
/**
* 捕获所有Exception(兜底处理)
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("全局异常捕获:", e); // 记录异常日志
return Result.fail("系统异常:" + e.getMessage());
}
/**
* 捕获NullPointerException(特定异常优先处理)
*/
@ExceptionHandler(NullPointerException.class)
public Result<Void> handleNullPointerException(NullPointerException e) {
log.error("空指针异常捕获:", e);
return Result.fail("空指针异常:" + e.getMessage());
}
/**
* 捕获ArithmeticException(如除零异常)
*/
@ExceptionHandler(ArithmeticException.class)
public Result<Void> handleArithmeticException(ArithmeticException e) {
log.error("算术异常捕获:", e);
return Result.fail("算术异常:" + e.getMessage());
}
// 可扩展:添加自定义业务异常处理(如UserNotLoginException)
}
步骤 2:异常匹配规则
当程序抛出异常时,Spring 会按 "最具体异常优先" 的原则匹配处理器:
- 例如抛出
NullPointerException
,会优先匹配handleNullPointerException
,而非兜底的handleException
; - 匹配逻辑由
ExceptionHandlerMethodResolver
实现,通过异常类型的继承深度排序(子类异常处理器优先)。
3.2 实战效果
-
访问
/test/t2
(执行10/0
),抛出ArithmeticException
,返回:json
{ "status": "FAIL", "errorMessage": "算术异常:/ by zero", "data": null }
-
访问
/test/t3
(执行null.length()
),抛出NullPointerException
,返回:json
{ "status": "FAIL", "errorMessage": "空指针异常:Cannot invoke \"String.length()\" because \"a\" is null", "data": null }
四、@ControllerAdvice 源码解析:全局能力的 "基石"
@ControllerAdvice
是实现统一数据返回和统一异常处理的核心注解,其本质是一个 @Component
(可被 Spring 扫描注入),通过属性(如 basePackages
)指定生效范围。
4.1 核心源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 本质是Spring组件
public @interface ControllerAdvice {
// 指定生效的包(默认所有包)
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
// 指定生效的类(如仅对UserController生效)
Class<?>[] assignableTypes() default {};
// 指定生效的注解(如仅对@RestController生效)
Class<? extends Annotation>[] annotations() default {};
}
4.2 生效原理
- 统一数据返回 :
DispatcherServlet
初始化时,通过initHandlerAdapters
加载RequestMappingHandlerAdapter
;该适配器会扫描所有@ControllerAdvice
标注的类,若实现ResponseBodyAdvice
,则加入requestResponseBodyAdvice
列表;当响应返回时,调用ResponseBodyAdvice
的beforeBodyWrite
方法进行封装。 - 统一异常处理 :
DispatcherServlet
初始化时,通过initHandlerExceptionResolvers
加载ExceptionHandlerExceptionResolver
;该解析器会扫描所有@ControllerAdvice
标注的类,提取@ExceptionHandler
方法存入exceptionHandlerAdviceCache
;当抛出异常时,通过ExceptionHandlerMethodResolver
匹配对应的处理器方法。
五、总结与实战建议
5.1 核心知识点总结
功能 | 实现方案 | 核心组件 / 注解 | 作用 |
---|---|---|---|
拦截器 | 实现 HandlerInterceptor + 配置 WebMvcConfigurer | HandlerInterceptor 、WebMvcConfigurer |
统一请求拦截(登录校验、权限控制) |
统一数据返回 | @ControllerAdvice + ResponseBodyAdvice | @ControllerAdvice 、ResponseBodyAdvice |
所有接口返回格式统一为 Result |
统一异常处理 | @ControllerAdvice + @ExceptionHandler | @ControllerAdvice 、@ExceptionHandler |
全局捕获异常,返回结构化错误响应 |
5.2 实战建议
- 拦截器排除静态资源:前端的 JS、CSS、图片等静态资源无需拦截,避免影响页面加载;
- 自定义业务异常 :在实际项目中,建议定义业务异常(如
UserNotLoginException
、PermissionDeniedException
),结合@ExceptionHandler
实现更精细的异常处理; - 日志记录 :在异常处理器中务必记录异常栈信息(如
log.error("异常信息:", e)
),便于问题排查; - 接口评审:统一功能处理需在项目初期确定(如返回格式、异常码规范),避免后期大规模修改。
通过本文的方案,可大幅提升 SpringBoot 项目的可维护性和前后端协作效率,是企业级开发的必备技能。