You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring应用中基于数据库配置的无注解自定义验证实现问询

基于数据库配置实现Spring动态实体验证需求

当前我的应用通过Spring验证注解(如@NotNull、@NotEmpty)实现实体验证,示例代码如下:

public class AccountCreateRequest {
    @NotNull
    private String accountName;
    @NotNull
    @Size(max = ACCOUNT_NUMBER_CHAR_LIMIT, message = "AccountNumber should not exceed {max} characters",groups = {SqlCharLimitValidatorGroup.class})
    private String accountNumber;
    private String departmentType;
    @NotNull
    private AccountType accountTypeId;
    private String accountSubTypeId;
}

该方案运行正常,还可基于分组实现不同流程的验证。

现有不同客户端对实体创建有不同验证需求,我不想创建多个验证分组,希望将客户端级验证配置存储在数据库中,基于配置驱动验证,例如针对上述请求的配置示例:

{ 
  "asset": "Account",
  "client": "123",
  "fields": [
    {
      "name": "accountName",
      "validations": ["NotNull", "NotBlank"]
    },
    ...
  ]
}

需实现针对任意实体,从数据库读取字段配置并执行验证,请问如何在Spring应用中实现该功能?


实现方案

1. 定义数据库配置模型

先创建对应数据库存储的实体类,映射客户端验证配置:

@Entity
public class ClientValidationConfig {
    @Id
    private Long id;
    private String asset; // 对应实体标识,如"Account"
    private String client; // 客户端ID
    @ElementCollection
    private List<FieldValidation> fields;

    // 内部类:映射字段验证规则
    public static class FieldValidation {
        private String name; // 实体字段名
        private List<String> validations; // 验证规则列表,如["NotNull", "NotBlank"]

        // getter、setter方法
    }

    // getter、setter方法
}

2. 编写自定义动态验证器

实现Spring的Validator接口,处理从数据库读取的动态验证规则:

@Component
public class DynamicClientValidator implements Validator {

    @Autowired
    private ClientValidationConfigRepository configRepo;

    @Override
    public boolean supports(Class<?> clazz) {
        // 支持所有实体类,也可根据asset标识做过滤
        return true;
    }

    @Override
    public void validate(Object target, Errors errors) {
        // 1. 获取当前请求的客户端ID(可从请求Header、ThreadLocal等获取)
        String clientId = ClientContext.getClientId();
        if (clientId == null) {
            return; // 无客户端ID时跳过动态验证
        }

        // 2. 根据实体标识查询对应配置
        String assetName = getAssetName(target.getClass());
        ClientValidationConfig config = configRepo.findByAssetAndClient(assetName, clientId);
        if (config == null) {
            return; // 无对应配置时跳过
        }

        // 3. 遍历字段配置,执行验证
        for (ClientValidationConfig.FieldValidation fieldConfig : config.getFields()) {
            String fieldName = fieldConfig.getName();
            Field field = ReflectionUtils.findField(target.getClass(), fieldName);
            if (field == null) {
                continue; // 实体无该字段,跳过
            }
            ReflectionUtils.makeAccessible(field);
            Object fieldValue = ReflectionUtils.getField(field, target);

            for (String validationRule : fieldConfig.getValidations()) {
                executeValidation(fieldName, fieldValue, validationRule, errors);
            }
        }
    }

    // 辅助方法:获取实体对应的asset标识(可自定义规则,比如取类名或实体上的自定义注解)
    private String getAssetName(Class<?> clazz) {
        // 示例:移除后缀"CreateRequest"得到资产标识
        return clazz.getSimpleName().replace("CreateRequest", "");
    }

    // 执行具体验证逻辑
    private void executeValidation(String fieldName, Object fieldValue, String validationRule, Errors errors) {
        switch (validationRule) {
            case "NotNull":
                if (fieldValue == null) {
                    errors.rejectValue(fieldName, "not.null", "字段不能为空");
                }
                break;
            case "NotBlank":
                if (fieldValue instanceof String && ((String) fieldValue).isBlank()) {
                    errors.rejectValue(fieldName, "not.blank", "字段不能为空白");
                }
                break;
            case "SizeMax":
                // 若需参数化验证,可将规则定义为"SizeMax:20",再解析参数
                if (fieldValue instanceof String && ((String) fieldValue).length() > 20) {
                    errors.rejectValue(fieldName, "size.max", "字段长度超出限制");
                }
                break;
            // 可扩展更多验证规则,如NotEmpty、Pattern等
            default:
                throw new IllegalArgumentException("不支持的验证规则:" + validationRule);
        }
    }
}

3. 整合到Spring验证流程

有两种方式将自定义验证器接入Spring验证体系:

  • 方式一:Controller手动调用
    在接口中先执行注解验证,再触发动态验证:
@RestController
public class AccountController {

    @Autowired
    private DynamicClientValidator dynamicValidator;

    @PostMapping("/accounts")
    public ResponseEntity<?> createAccount(@RequestBody AccountCreateRequest request, BindingResult result) {
        // 先执行Spring注解式验证
        // 再执行动态验证
        dynamicValidator.validate(request, result);
        if (result.hasErrors()) {
            return ResponseEntity.badRequest().body(result.getAllErrors());
        }
        // 业务逻辑处理
        return ResponseEntity.ok();
    }
}
  • 方式二:全局注册验证器
    通过WebMvcConfigurer注册全局验证器,让@Valid注解自动触发动态验证:
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private DynamicClientValidator dynamicValidator;

    @Override
    public void addValidators(ValidatorRegistry registry) {
        // 添加自定义验证器,Spring会先执行注解验证,再执行动态验证
        registry.addValidator(dynamicValidator);
    }
}

之后Controller只需使用@Valid即可:

@PostMapping("/accounts")
public ResponseEntity<?> createAccount(@Valid @RequestBody AccountCreateRequest request, BindingResult result) {
    if (result.hasErrors()) {
        return ResponseEntity.badRequest().body(result.getAllErrors());
    }
    // 业务逻辑处理
    return ResponseEntity.ok();
}

4. 优化与扩展方向

  • 参数化验证支持:将验证规则改为带参数格式(如"Size:max=10"),解析参数后执行对应验证逻辑。
  • 配置缓存:用Spring Cache缓存asset+clientId对应的配置,减少数据库查询次数。
  • 自定义实体标识注解:给实体类添加@Asset("Account")注解,明确映射数据库中的asset字段,避免类名解析的耦合。
  • 策略模式解耦验证逻辑:将每种验证规则抽象为ValidationStrategy接口实现类,通过Map<String, ValidationStrategy>注入,替代switch分支,便于扩展。

内容的提问来源于stack exchange,提问作者sabu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 02:35:19