使用自定义验证器在地图上进行 JSR-303 验证
JSR-303 Validation on Map with custom validator
我正在使用 Spring 和 Thymeleaf 填写表格:
<form method="post" th:action="@{/postForm}" th:object="${myForm}"><!--/* model.addAttribute("myForm", new MyForm()) */-->
<input type="text" th:each="id : ${idList}" th:field="*{map['__${id}__']}" /><!--/* results in map['P12345'] */-->
</form>
我的表格看起来像这样:
public class MyForm {
@Quantity
private Map<String, String> map = new HashMap<String, String>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
如您所见,我做了一个自定义注释 @Quantity
,它应该检查输入值是否可以解析为 BigDecimal
:
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = QuantityValidator.class)
@Documented
public @interface Quantity {
String message() default "{com.example.form.validation.constraints.Quantity}";
Class<? extends Payload>[] payload() default {};
Class<?>[] groups() default {};
}
public class QuantityValidator implements ConstraintValidator<Quantity, Map<String, String>> {
private final DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();
private final ParsePosition pos = new ParsePosition(0);
@Override
public void initialize(Quantity quantity) {
format.setParseBigDecimal(true);
}
@Override
public boolean isValid(Map<String, String> map, ConstraintValidatorContext context) {
List<String> invalidFieldsList = new ArrayList<String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String quantity = entry.getValue();
if (quantity != null && !quantity.isEmpty()) {
if ((BigDecimal) format.parse(quantity, pos) == null) {
invalidFieldsList.add(entry.getKey());
}
}
}
if (!invalidFieldsList.isEmpty()) {
context.disableDefaultConstraintViolation();
for (String field : invalidFieldsList) {
context.buildConstraintViolationWithTemplate("Invalid Quantity for Field: " + field).addNode(field).addConstraintViolation();
}
return false;
}
return true;
}
}
现在在我的控制器中 class 我正在这样做:
@Controller
public class MyController {
@RequestMapping(value = "/postForm", method = RequestMethod.POST)
public void postForm(@ModelAttribute @Valid MyForm myForm, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) {
// do some stuff
}
}
}
但是当尝试将 d
放入文本字段以测试验证时得到 NotReadablePropertyException
:
java.lang.IllegalStateException: JSR-303 validated property 'map.P12345' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)
Caused by:
org.springframework.beans.NotReadablePropertyException: Invalid property 'map.P12345' of bean class [com.example.form.MyForm]: Bean property 'map.P12345' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:726) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:717) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:283) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:143) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
... 84 more
这是我读过的示例,我想用自定义验证器进行扩展:http://viralpatel.net/blogs/spring-mvc-hashmap-form-example/
编辑:
注释掉 @Valid
注释并检查 myForm.getMap() 包含的内容是否正确填充地图时:
@Controller
public class MyController {
private final Logger log = LogManager.getLogger(getClass());
@RequestMapping(value = "/postForm", method = RequestMethod.POST)
public void postForm(@ModelAttribute /*@Valid*/ MyForm myForm, BindingResult bindingResult) {
// Output:
// P12345: d
// P67890:
for (Map.Entry<String, String> entry : myForm.getMap().entrySet()) {
log.debug(entry.getKey() + ": " + entry.getValue());
}
}
}
ConstraintValidatorContext
假定您正在构建指向对象图中实际可导航属性的路径。 Bean Validation 实际上并没有对此进行验证,因此理论上您可以添加任何内容,但 Spring 集成似乎确实使用了该路径。可能是为了将错误映射到右边的 UI 元素(我不知道 Spring 代码)。您必须做的是确保为正确的节点添加约束违规。 API 实际上允许遍历地图。看起来像:
context.buildConstraintViolationWithTemplate( message )
.addPropertyNode( "foo" )
.addPropertyNode( null ).inIterable().atKey( "test" )
.addConstraintViolation();
'null' 在这种情况下表示映射到键的值。这与向值本身的属性添加违规形成对比,如下所示:
context.buildConstraintViolationWithTemplate( message )
.addPropertyNode( "foo" )
.addPropertyNode( "bar" ).inIterable().atKey( "test" )
我正在使用 Spring 和 Thymeleaf 填写表格:
<form method="post" th:action="@{/postForm}" th:object="${myForm}"><!--/* model.addAttribute("myForm", new MyForm()) */-->
<input type="text" th:each="id : ${idList}" th:field="*{map['__${id}__']}" /><!--/* results in map['P12345'] */-->
</form>
我的表格看起来像这样:
public class MyForm {
@Quantity
private Map<String, String> map = new HashMap<String, String>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
如您所见,我做了一个自定义注释 @Quantity
,它应该检查输入值是否可以解析为 BigDecimal
:
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = QuantityValidator.class)
@Documented
public @interface Quantity {
String message() default "{com.example.form.validation.constraints.Quantity}";
Class<? extends Payload>[] payload() default {};
Class<?>[] groups() default {};
}
public class QuantityValidator implements ConstraintValidator<Quantity, Map<String, String>> {
private final DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();
private final ParsePosition pos = new ParsePosition(0);
@Override
public void initialize(Quantity quantity) {
format.setParseBigDecimal(true);
}
@Override
public boolean isValid(Map<String, String> map, ConstraintValidatorContext context) {
List<String> invalidFieldsList = new ArrayList<String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String quantity = entry.getValue();
if (quantity != null && !quantity.isEmpty()) {
if ((BigDecimal) format.parse(quantity, pos) == null) {
invalidFieldsList.add(entry.getKey());
}
}
}
if (!invalidFieldsList.isEmpty()) {
context.disableDefaultConstraintViolation();
for (String field : invalidFieldsList) {
context.buildConstraintViolationWithTemplate("Invalid Quantity for Field: " + field).addNode(field).addConstraintViolation();
}
return false;
}
return true;
}
}
现在在我的控制器中 class 我正在这样做:
@Controller
public class MyController {
@RequestMapping(value = "/postForm", method = RequestMethod.POST)
public void postForm(@ModelAttribute @Valid MyForm myForm, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) {
// do some stuff
}
}
}
但是当尝试将 d
放入文本字段以测试验证时得到 NotReadablePropertyException
:
java.lang.IllegalStateException: JSR-303 validated property 'map.P12345' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)
Caused by:
org.springframework.beans.NotReadablePropertyException: Invalid property 'map.P12345' of bean class [com.example.form.MyForm]: Bean property 'map.P12345' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:726) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:717) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:283) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:143) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
... 84 more
这是我读过的示例,我想用自定义验证器进行扩展:http://viralpatel.net/blogs/spring-mvc-hashmap-form-example/
编辑:
注释掉 @Valid
注释并检查 myForm.getMap() 包含的内容是否正确填充地图时:
@Controller
public class MyController {
private final Logger log = LogManager.getLogger(getClass());
@RequestMapping(value = "/postForm", method = RequestMethod.POST)
public void postForm(@ModelAttribute /*@Valid*/ MyForm myForm, BindingResult bindingResult) {
// Output:
// P12345: d
// P67890:
for (Map.Entry<String, String> entry : myForm.getMap().entrySet()) {
log.debug(entry.getKey() + ": " + entry.getValue());
}
}
}
ConstraintValidatorContext
假定您正在构建指向对象图中实际可导航属性的路径。 Bean Validation 实际上并没有对此进行验证,因此理论上您可以添加任何内容,但 Spring 集成似乎确实使用了该路径。可能是为了将错误映射到右边的 UI 元素(我不知道 Spring 代码)。您必须做的是确保为正确的节点添加约束违规。 API 实际上允许遍历地图。看起来像:
context.buildConstraintViolationWithTemplate( message )
.addPropertyNode( "foo" )
.addPropertyNode( null ).inIterable().atKey( "test" )
.addConstraintViolation();
'null' 在这种情况下表示映射到键的值。这与向值本身的属性添加违规形成对比,如下所示:
context.buildConstraintViolationWithTemplate( message )
.addPropertyNode( "foo" )
.addPropertyNode( "bar" ).inIterable().atKey( "test" )