Spring Boot 实际开发中,对输入参数进行合法性校验的几种方案

在 Spring Boot 实际开发中,对输入参数进行合法性校验,确保其值是某个枚举类型中定义的值,常见的实现方案有以下几种:

方案一:手动校验

手动在业务逻辑中进行枚举值的合法性检查。

// 定义状态枚举
enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}

 
// 控制器类
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
public class StatusController {
 
    // 处理请求的方法,接收一个包含状态的Map对象
    @PostMapping("/status")
    public Map handleStatus(@RequestBody Map request) {
        Map response = new HashMap<>();
        // 从请求中获取状态值
        String statusStr = request.get("status");
        try {
            // 尝试将输入的字符串转换为枚举类型
            Status status = Status.valueOf(statusStr);
            response.put("message", "状态合法");
            response.put("status", status);
        } catch (IllegalArgumentException e) {
            // 若转换失败,说明输入的状态值不合法
            response.put("message", "状态不合法");
        }
        return response;
    }
}

解释
运用知识:Java 的枚举类型、异常处理。
思想:简单直接,在业务逻辑中手动进行校验。通过Status.valueOf(statusStr)尝试将输入的字符串转换为枚举类型,如果输入的字符串不是枚举中定义的值,会抛出IllegalArgumentException异常,捕获该异常并返回错误信息。

方案二:使用自定义注解和 Spring Validation

使用自定义注解结合 Spring Validation 框架进行校验。

示例代码

// 定义状态枚举
enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}
 
// 自定义注解,用于校验枚举值
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
 
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@Documented
@Constraint(validatedBy = EnumValidator.class)
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface EnumValid {
    // 错误消息
    String message() default "输入的值不是合法的枚举值";
    // 校验组
    Class[] groups() default {};
    // 负载
    Class[] payload() default {};
    // 枚举类
    Class> enumClass();
}
 
// 自定义校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;
 
public class EnumValidator implements ConstraintValidator {
    private Class> enumClass;
 
    @Override
    public void initialize(EnumValid enumValid) {
        // 获取注解中指定的枚举类
        this.enumClass = enumValid.enumClass();
    }
 
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        try {
            // 获取枚举类的values方法
            Method method = enumClass.getMethod("values");
            // 调用values方法获取枚举值数组
            Enum[] enumValues = (Enum[]) method.invoke(null);
            for (Enum enumValue : enumValues) {
                if (enumValue.name().equals(value)) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
 
// 请求实体类
import javax.validation.constraints.NotBlank;
 
public class StatusRequest {
    // 使用自定义注解进行校验
    @EnumValid(enumClass = Status.class, message = "状态不合法")
    @NotBlank(message = "状态不能为空")
    private String status;
 
    public String getStatus() {
        return status;
    }
 
    public void setStatus(String status) {
        this.status = status;
    }
}
 
// 控制器类
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
 
import javax.validation.Valid;
 
@RestController
@Validated
public class StatusController {
 
    @PostMapping("/status")
    public ResponseEntity handleStatus(@Valid @RequestBody StatusRequest request, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 如果有校验错误,返回错误信息
            return new ResponseEntity<>(bindingResult.getFieldError().getDefaultMessage(), HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>("状态合法", HttpStatus.OK);
    }
}

解释
运用知识:Java 注解、Spring Validation 框架、反射。
思想:通过自定义注解EnumValid和对应的校验器EnumValidator,将校验逻辑封装起来。在请求实体类的字段上使用自定义注解进行校验,Spring Validation 框架会自动调用校验器进行校验。使用反射机制动态获取枚举类的所有值,提高了代码的灵活性和可扩展性。

方案三:使用 AOP(面向切面编程)

可以使用 AOP(面向切面编程)来实现对输入参数是否为合法枚举值的校验。AOP 允许我们在不修改原有业务逻辑的基础上,对方法的执行进行增强,通过在方法执行前对输入参数进行校验,可以将校验逻辑从业务逻辑中分离出来,提高代码的可维护性和可扩展性。

实现步骤及示例代码

1. 定义状态枚举

// 定义状态枚举,包含合法的状态值
enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}

2. 定义自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
// 自定义注解,用于标记需要进行枚举值校验的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidation {
    // 指定需要校验的枚举类
    Class> enumClass();
    // 指定需要校验的参数索引,从0开始
    int paramIndex();
}

3. 创建 AOP 切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
 
import java.lang.reflect.Method;
import java.util.Arrays;
 
@Aspect
@Component
public class EnumValidationAspect {
 
    // 定义前置通知,在目标方法执行前进行校验
    @Before("@annotation(com.example.demo.EnumValidation)")
    public void validateEnum(JoinPoint joinPoint) throws Exception {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取目标方法
        Method method = signature.getMethod();
        // 获取方法上的EnumValidation注解
        EnumValidation enumValidation = method.getAnnotation(EnumValidation.class);
        // 获取注解中指定的枚举类
        Class> enumClass = enumValidation.enumClass();
        // 获取注解中指定的参数索引
        int paramIndex = enumValidation.paramIndex();
        // 获取目标方法的所有参数
        Object[] args = joinPoint.getArgs();
        if (paramIndex < args.length) {
            // 获取需要校验的参数值
            Object paramValue = args[paramIndex];
            if (paramValue instanceof String) {
                String value = (String) paramValue;
                // 获取枚举类的所有值
                Enum[] enumConstants = enumClass.getEnumConstants();
                // 检查输入值是否为合法的枚举值
                boolean isValid = Arrays.stream(enumConstants)
                       .anyMatch(e -> e.name().equals(value));
                if (!isValid) {
                    // 若不是合法的枚举值,抛出异常
                    throw new IllegalArgumentException("输入的值不是合法的枚举值: " + value);
                }
            }
        }
    }
}

4. 创建控制器类

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.Map;
 
@RestController
public class StatusController {
 
    // 使用自定义注解进行枚举值校验
    @EnumValidation(enumClass = Status.class, paramIndex = 0)
    @PostMapping("/status")
    public String handleStatus(@RequestBody Map request) {
        // 若校验通过,处理业务逻辑
        String status = request.get("status");
        return "状态合法: " + status;
    }
}

解释
运用知识:Java 注解、AOP(Spring AOP)、反射。
思想:
解耦:将枚举值的校验逻辑从业务逻辑中分离出来,通过自定义注解标记需要进行校验的方法,AOP 切面类在方法执行前对指定参数进行校验,不影响原有业务逻辑的实现。
可复用性:自定义注解和 AOP 切面类可以在多个方法中复用,只需要在需要校验的方法上添加注解并指定相应的枚举类和参数索引即可。
扩展性:如果需要对不同的枚举类型和参数进行校验,只需要修改注解的属性值,无需修改大量代码。
通过这种方式,AOP 为参数校验提供了一种灵活、可扩展的解决方案。

方案四:结合 Spring 的 HandlerMethodArgumentResolver 来实现

除了前面提到的手动校验、使用自定义注解结合 Spring Validation 和 AOP 实现参数枚举值校验之外,还可以结合 Spring 的 HandlerMethodArgumentResolver 来实现一个较为高级的方案。这种方案的核心思想是自定义参数解析器,在解析参数的过程中完成枚举值的校验,这样可以将校验逻辑集中处理,并且对控制器方法更加透明。

实现步骤及示例代码

1. 定义状态枚举

// 定义状态枚举,包含合法的状态值
enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}

2. 定义自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
// 自定义注解,用于标记需要进行枚举值校验的参数
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidParam {
    // 指定需要校验的枚举类
    Class> enumClass();
}

3. 创建自定义参数解析器

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
 
import java.lang.reflect.Method;
import java.util.Arrays;
 
// 自定义参数解析器,实现 HandlerMethodArgumentResolver 接口
public class EnumValidParamResolver implements HandlerMethodArgumentResolver {
 
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 判断参数是否使用了 EnumValidParam 注解
        return parameter.hasParameterAnnotation(EnumValidParam.class);
    }
 
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 获取参数上的 EnumValidParam 注解
        EnumValidParam enumValidParam = parameter.getParameterAnnotation(EnumValidParam.class);
        // 获取注解中指定的枚举类
        Class> enumClass = enumValidParam.enumClass();
        // 获取参数名
        String paramName = parameter.getParameterName();
        // 从请求中获取参数值
        String paramValue = webRequest.getParameter(paramName);
        if (paramValue != null) {
            // 获取枚举类的所有值
            Enum[] enumConstants = enumClass.getEnumConstants();
            // 检查输入值是否为合法的枚举值
            boolean isValid = Arrays.stream(enumConstants)
                   .anyMatch(e -> e.name().equals(paramValue));
            if (isValid) {
                // 若合法,将字符串转换为枚举类型
                Method valueOfMethod = enumClass.getMethod("valueOf", String.class);
                return valueOfMethod.invoke(null, paramValue);
            } else {
                // 若不合法,抛出异常
                throw new IllegalArgumentException("输入的值不是合法的枚举值: " + paramValue);
            }
        }
        return null;
    }
}

4. 配置自定义参数解析器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
import java.util.List;
 
// 配置类,将自定义参数解析器添加到 Spring MVC 配置中
@Configuration
public class WebConfig implements WebMvcConfigurer {
 
    @Override
    public void addArgumentResolvers(List resolvers) {
        resolvers.add(new EnumValidParamResolver());
    }
}

5. 创建控制器类

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/status")
public class StatusController {
 
    // 使用自定义注解进行枚举值校验
    @GetMapping
    public String handleStatus(@EnumValidParam(enumClass = Status.class) Status status) {
        // 若校验通过,处理业务逻辑
        return "状态合法: " + status;
    }
}

解释
运用知识:Java 注解、Spring 的 HandlerMethodArgumentResolver、反射。
思想:
参数解析与校验一体化:通过自定义参数解析器,在解析参数的同时完成枚举值的校验。这样可以将校验逻辑集中在参数解析阶段,避免在控制器方法中重复编写校验代码,使控制器方法更加简洁。
对控制器透明:控制器方法只需要使用自定义注解标记需要校验的参数,不需要关心具体的校验逻辑,校验逻辑由参数解析器统一处理,提高了代码的可维护性和可扩展性。
可复用性:自定义注解和参数解析器可以在多个控制器方法中复用,只需要在需要校验的参数上添加注解并指定相应的枚举类即可。
这种方案通过自定义参数解析器将枚举值校验与参数解析结合起来,是一种较为高级且灵活的实现方式。

你可能感兴趣的:(Spring Boot 实际开发中,对输入参数进行合法性校验的几种方案)