Spring中JSR-303验证myDto.mail无对应访问器的问题解决
我有如下DTO类结构:
public class MyDto { @Email private String mail; public String getMail() { return mail; } public void setMail(String mail) { this.mail = mail; } } @Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-01T17:12:49.075897700+03:00[Europe/Moscow]") public class CustomUserPatchRequestDto { @JsonProperty("myDto") @Valid private JsonNullable<@Valid MyDto> myDto; // getter and setter ...
控制器代码:
@RestController @Validated class MyController() { @RequestMapping( method = [PATCH], value = ["/userscustom/{id}"], produces = ["application/json"], consumes = ["application/merge-patch+json"] ) fun patchUserCustom( @Parameter(name = "id", description = "", required = true,in= PATH) @PathVariable("id") id: String? @Valid @RequestBody userPatchRequestDto: CustomUserPatchRequestDto? ): ResponseEntity<String?>? { return ResponseEntity.ok("ok") }
当发送以下请求体时:
{ "myDto": { "mail": "12sdfdsf3" } }
收到500错误响应:
{ "code": 500, "reason": "JSR-303 validated property 'myDto.mail' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)" }
我尝试过在各处添加@Valid注解,但没有效果。
尝试过的解决方案
我在控制器中添加了@InitBinder:
@InitBinder private fun activateDirectFieldAccess(dataBinder: DataBinder) { println("Working Inner") dataBinder.initDirectFieldAccess(); }
也尝试过添加独立的ControllerAdvice类:
@ControllerAdvice class CustomControllerAdvice { @InitBinder private fun activateDirectFieldAccess(dataBinder: DataBinder) { println("Working Inner") dataBinder.initDirectFieldAccess(); } }
两种情况控制台都输出了"Working Inner",说明代码已执行,但问题依旧存在。
补充说明
奇怪的是,当传入格式正确的邮箱时没有任何错误,一切正常。问题仅在传入无效邮箱时出现,且表现为上述异常。
这个问题的核心是Spring在验证嵌套的JsonNullable对象时,无法正确解析属性路径——JsonNullable是OpenAPI生成的包装类,Spring默认验证器无法直接识别其内部的MyDto实例,导致验证失败时找不到对应的属性访问器。
解决步骤:
自定义
JsonNullable验证适配器
创建自定义验证器,主动处理JsonNullable内部对象的验证逻辑:import org.springframework.validation.Validator; import org.springframework.validation.Errors; import org.openapitools.jackson.nullable.JsonNullable; import javax.validation.Validation; import javax.validation.ValidatorFactory; import javax.validation.Validator; public class JsonNullableValidator implements Validator { private final Validator beanValidator; public JsonNullableValidator() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); this.beanValidator = factory.getValidator(); } @Override public boolean supports(Class<?> clazz) { return JsonNullable.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { JsonNullable<?> jsonNullable = (JsonNullable<?>) target; if (jsonNullable.isPresent()) { Object value = jsonNullable.get(); beanValidator.validate(value, errors); } } }注册自定义验证器
在控制器的@InitBinder中注册这个验证器,替换默认处理逻辑:@InitBinder fun initBinder(dataBinder: DataBinder) { dataBinder.addValidators(JsonNullableValidator()) dataBinder.initDirectFieldAccess() }调整DTO注解
可以去掉JsonNullable字段上的@Valid,让自定义验证器统一处理内部对象的验证:public class CustomUserPatchRequestDto { @JsonProperty("myDto") private JsonNullable<@Valid MyDto> myDto; // getter and setter ... }
原理说明
当邮箱格式无效时,自定义验证器会主动检查JsonNullable是否包含有效值,然后对内部的MyDto实例执行验证,同时正确传递错误信息,避免Spring默认验证器因无法识别JsonNullable结构而抛出属性访问器异常。
另外,也可以尝试升级OpenAPI生成器版本,部分新版本已修复JsonNullable与Spring验证的兼容性问题。
内容的提问来源于stack exchange,提问作者gstackoverflow

