本文目录导读:

- 核心思想
- 方案一:Java Bean Validation(最推荐,Spring Boot 标配)
- 方案二:Spring AOP + 自定义注解(灵活度高)
- 方案三:Spring 的 Assert 工具类(简单直接,适合单方法)
- 方案四:前置过滤器或拦截器(适合所有请求)
- 总结:如何选择?
针对“空参数校验拦截”,这是一个后端开发中非常常见且重要的需求,最优雅、高效的解决方案通常不是写一堆 if (xxx == null) 判断,而是利用框架提供的注解或内置的校验机制。
下面按照技术栈和实现层级来给出几种主流方案,并包含代码示例。
核心思想
空参数主要分为两类:
- null 值:对象没有传过来。
- 空字符串:传了 或 (空格)。
最好的方案是在进入业务逻辑之前就统一拦截掉,这通常通过参数校验(Validation)和全局异常处理来实现。
Java Bean Validation(最推荐,Spring Boot 标配)
这是最规范、侵入性最低的做法,利用 javax.validation 或 jakarta.validation 包下的注解。
在实体类上添加注解
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotEmpty;
import java.util.List;
public class UserRequest {
// NotNull: 不能为null,但可以为 "" (空字符串)
@NotNull(message = "用户ID不能为空")
private Long id;
// @NotBlank: 不能为null,且去除前后空格后长度必须>0 (最常用)
@NotBlank(message = "用户名不能为空")
private String username;
// @NotEmpty: 不能为null,且长度必须>0 (可用于String, Collection, Map, Array)
@NotEmpty(message = "地址列表不能为空")
private List<String> addresses;
// getters and setters...
}
在 Controller 中启用校验
在需要校验的参数前加上 @Valid 或 @Validated 注解。
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class UserController {
@PostMapping("/user")
// 注意:这里加了 @Valid
public Result createUser(@Valid @RequestBody UserRequest request) {
// 如果校验失败,方法体不会被执行,会抛出 MethodArgumentNotValidException
System.out.println("创建用户: " + request.getUsername());
return Result.success();
}
}
全局异常拦截器(统一返回格式)
如果不加这个,Spring 会返回默认的错误页面(400 Bad Request),需要将其转化为统一的 JSON 格式。
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice // 全局异常处理
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleValidationExceptions(MethodArgumentNotValidException ex) {
// 提取第一个错误信息
FieldError fieldError = ex.getBindingResult().getFieldError();
String errorMessage = fieldError != null ? fieldError.getDefaultMessage() : "参数校验失败";
return Result.error(400, errorMessage);
}
// 也可以处理单个参数校验失败 (@RequestParam 上的校验)
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleConstraintViolation(ConstraintViolationException ex) {
String errorMessage = ex.getConstraintViolations().iterator().next().getMessage();
return Result.error(400, errorMessage);
}
}
Spring AOP + 自定义注解(灵活度高)
如果你不想修改实体类,或者想拦截更复杂的场景(比如对所有方法的第一参数进行非空校验)。
定义自定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullParams {
// 可以定义要校验的字段名
String[] value() default {};
}
编写 AOP 切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ParamsValidAspect {
@Around("@annotation(notNullParams)")
public Object checkParams(ProceedingJoinPoint joinPoint, NotNullParams notNullParams) throws Throwable {
Object[] args = joinPoint.getArgs();
// 遍历参数,这里简单判断是否为null
for (Object arg : args) {
// 针对 String 类型,可以进一步判断空字符串
if (arg instanceof String) {
String str = (String) arg;
if (str == null || str.trim().isEmpty()) {
throw new IllegalArgumentException("字符串参数不能为空");
}
} else if (arg == null) {
throw new IllegalArgumentException("参数不能为null");
}
// 可以递归检查复杂对象内部字段...
}
// 校验通过,执行原方法
return joinPoint.proceed();
}
}
使用
@NotNullParams
public void createOrder(String userId, String productId) {
// 业务逻辑
}
Spring 的 Assert 工具类(简单直接,适合单方法)
如果你不想引入 AOP 或复杂注解,Spring 内置的 Assert 类非常方便。
import org.springframework.util.Assert;
public class OrderService {
public void processOrder(String orderId, Long userId) {
// 如果不满足条件,直接抛出 IllegalArgumentException
Assert.hasText(orderId, "订单ID不能为空"); // 检查非null且非空字符串
Assert.notNull(userId, "用户ID不能为空"); // 检查非null
// 业务逻辑...
}
}
缺点:需要在每个方法里重复写,代码冗余。
前置过滤器或拦截器(适合所有请求)
如果你想在 Controller 层之前拦截,可以通过 Filter 或 HandlerInterceptor 检查请求参数。
拦截器实现
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class ParamNullInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取所有参数
java.util.Map<String, String[]> params = request.getParameterMap();
for (java.util.Map.Entry<String, String[]> entry : params.entrySet()) {
String[] values = entry.getValue();
if (values == null || values.length == 0 || values[0] == null || values[0].trim().isEmpty()) {
// 参数为空,返回错误响应
response.setContentType("application/json");
response.getWriter().write("{\"code\":400,\"msg\":\"参数 [" + entry.getKey() + "] 不能为空\"}");
return false; // 拦截
}
}
return true; // 放行
}
}
注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ParamNullInterceptor())
.addPathPatterns("/api/**"); // 拦截所有/api/下的请求
}
}
如何选择?
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Bean Validation (方案一) | 绝大多数后端接口,尤其是增删改查 | 标准化、声明式编程、与Spring高度集成 | 需要定义实体类/DTO |
| Spring AOP (方案二) | 需要对特定方法统一拦截、参数校验逻辑复杂 | 灵活、无侵入、可批量处理 | 需要引入AOP依赖,调试稍麻烦 |
| Assert 工具类 (方案三) | 简单的工具方法、临时调试 | 最简单直接,无额外依赖 | 重复代码多,难以维护 |
| 拦截器/过滤器 (方案四) | 全局强制校验(如所有接口都不允许空参) | 在进入Controller前拦截,覆盖面广 | 粒度粗,无法精细化校验字段含义 |
最推荐的做法:
日常开发中,应首先选择 方案一 (Bean Validation + 全局异常处理),这是最优雅、最符合 Spring 设计哲学的解决方案,只有在无法修改实体类或需要非常特殊的校验逻辑时,才考虑 AOP 或拦截器。