空参数如何校验拦截

wen 开源项目 27

本文目录导读:

空参数如何校验拦截

  1. 核心思想
  2. 方案一:Java Bean Validation(最推荐,Spring Boot 标配)
  3. 方案二:Spring AOP + 自定义注解(灵活度高)
  4. 方案三:Spring 的 Assert 工具类(简单直接,适合单方法)
  5. 方案四:前置过滤器或拦截器(适合所有请求)
  6. 总结:如何选择?

针对“空参数校验拦截”,这是一个后端开发中非常常见且重要的需求,最优雅、高效的解决方案通常不是写一堆 if (xxx == null) 判断,而是利用框架提供的注解或内置的校验机制。

下面按照技术栈实现层级来给出几种主流方案,并包含代码示例。

核心思想

空参数主要分为两类:

  1. null 值:对象没有传过来。
  2. 空字符串:传了 或 (空格)。

最好的方案是在进入业务逻辑之前就统一拦截掉,这通常通过参数校验(Validation)全局异常处理来实现。


Java Bean Validation(最推荐,Spring Boot 标配)

这是最规范、侵入性最低的做法,利用 javax.validationjakarta.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 层之前拦截,可以通过 FilterHandlerInterceptor 检查请求参数。

拦截器实现

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 或拦截器。

抱歉,评论功能暂时关闭!