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
相关产品推荐
相关产品推荐

