Spring bean 验证消息解析
Spring bean validation messages resolution
我想做一个非常具体的任务,获取 Object
中每个字段的所有验证消息。
第一个任务很简单,获取 Object
中字段的所有 Annotations
,同样递归地,已经完成。
(从百里香的 html5val 方言修改代码)
private List<Annotation> fieldAnnotations() {
Field field = this.fieldFinder.findField(this.targetClass, this.targetFieldName);
if (field != null) {
List<Annotation> annotations = Arrays.asList(field.getAnnotations());
List<Annotation> toAdd = new ArrayList<>();
for(Annotation a:annotations)
if(a.annotationType().isAssignableFrom(Valid.class)){
toAdd.addAll(new AnnotationExtractor(field.getType()).getAnnotationsForField(this.targetFieldName));
}
else
toAdd.add(a);
return toAdd;
}
return Collections.emptyList();
}
现在我正在尝试获取每个注释的国际化消息。
BeanPropertyBindingResult binding = new BeanPropertyBindingResult(realObject, root);
for (Annotation constraint : constraints) {
String message = AnnotationExtractor.getDefaultMessage(constraint);
binding.rejectValue(fieldName, constraint.annotationType().getSimpleName(), message);
}
List<ObjectError> errors = binding.getAllErrors();
RequestContext requestContext = (RequestContext) arguments.getContext().getVariables().get(SpringContextVariableNames.SPRING_REQUEST_CONTEXT);
for(ObjectError e:errors){
String s =requestContext.getMessage(e, true);
}
我正在收到国际化消息,如果 MessageSource
可以解决某些问题,那就太好了!
遗憾的是,我无法获取默认消息的消息,例如 org.hibernate.validator.constraints.Length.message
,但这不是问题(我总是可以通过我的 MessageSource
提供这些消息)。
为了让这个任务充分发挥作用,我错过了一件事,消息的参数。因此,已解决的消息看起来像这样。
Alias length must be between {2} and {1}
。 BeanPropertyBindingResult
有一种方法可以拒绝带有参数的值,但我不知道如何从注释中获取它。它可能是由 Validator
实施完成的,对吧?
它与 Spring DataBinder 对无效字段所做的工作相同,但我想在 HTML5 表单验证中获取自定义验证消息的消息。有谁知道如何通过 Spring 内部使用对象推送 bean 以获取这些消息?
还有一件重要的事情,一切都在 thymeleaf 上下文中(这是我对 html5val 方言的修改)
解决方案很难看。
完整的解决方案在我的 HTML5 Validation Dialect 分支中
https://bitbucket.org/globalbus/html5-validator-dialect-fork/commits/85da11b2c13dfbd90ff05a55014d792cb453d5e2
首先,我需要 SpringValidatorAdapter.getArgumentsForConstraints()
成为 public。
public class MyValidationAdapter extends SpringValidatorAdapter{
public MyValidationAdapter(Validator targetValidator) {
super(targetValidator);
}
@Override
public Object[] getArgumentsForConstraint(String objectName,
String field, ConstraintDescriptor<?> descriptor) {
return super.getArgumentsForConstraint(objectName, field, descriptor);
}
@Override
public void processConstraintViolations(
Set<ConstraintViolation<Object>> violations, Errors errors) {
super.processConstraintViolations(violations, errors);
}
}
我们需要向构造函数提供一个 Validator
对象。我们可以从传递给 Dialect
的 org.thymeleaf.Arguments
中获取它
this.requestContext = (RequestContext) this.arguments.getContext().getVariables().get(SpringContextVariableNames.SPRING_REQUEST_CONTEXT);
this.validatorFactory = this.requestContext.getWebApplicationContext().getBean(ValidatorFactory.class);
this.validator = new MyValidationAdapter(this.validatorFactory.getValidator());
现在,代码真的很乱
public void processField(Element fieldElement, String fieldName){
BeanPropertyBindingResult binding = new BeanPropertyBindingResult(
this.realObject, this.root);
PropertyDescriptor rootProp = this.beanDescriptor
.getConstraintsForProperty(fieldName);
List<PropertyDescriptor> finalProp = new LinkedList<PropertyDescriptor>();
if (rootProp.isCascaded())// if it's nested, scan all properties for
// annotation
finalProp.addAll(this.validator.getConstraintsForClass(
rootProp.getElementClass()).getConstrainedProperties());
else
finalProp.add(rootProp);
for (PropertyDescriptor prop : finalProp)
for (final ConstraintDescriptor<?> desc : prop
.getConstraintDescriptors()) {
Annotation constraint = desc.getAnnotation();
String className = this.beanDescriptor.getElementClass()
.getSimpleName();
className = Character.toLowerCase(className.charAt(0))
+ className.substring(1);
String field = className + "." + prop.getPropertyName();
String errorCode = constraint.annotationType().getSimpleName();
Object[] errorArgs = this.validator.getArgumentsForConstraint(
this.root, field, desc);
String message = (String) desc.getAttributes().get(ANNOTATION_MESSAGE);
if (INTERNAL.matcher(message).find())
message = this.validatorFactory.getMessageInterpolator().interpolate(
message, new Context() {
public Object getValidatedValue() {
return null;
}
public ConstraintDescriptor<?> getConstraintDescriptor() {
return desc;
}
});
String[] errorCodes = binding.resolveMessageCodes(errorCode,
field);
binding.addError(new FieldError(binding.getObjectName(), prop
.getPropertyName(), null, false, errorCodes, errorArgs,
message));
}
List<ObjectError> errors = binding.getAllErrors();
StringBuilder customValidation = new StringBuilder();
for (ObjectError e : errors) {
String s = this.requestContext.getMessage(e, true);
customValidation.append(s);
if (!s.endsWith("."))
customValidation.append(". ");
}
String onInvalid = String.format("this.setCustomValidity('%s')",
customValidation.toString());
fieldElement.setAttribute(ONINVALID_ATTR, onInvalid);
fieldElement.setAttribute(ONCHANGE_ATTR, "this.setCustomValidity('')");
}
线索在SpringValidatorAdapter.getArgumentsForConstraints()
,参数有效,可以为消息解析提供参数。
MessageInterpolator.interpolate()
可以提供内部 Hibernate 消息
RequestContext.getMessage()
.
提供了最终解决方案
这不是 "clean solution",我必须在更多用例中测试它。
我想做一个非常具体的任务,获取 Object
中每个字段的所有验证消息。
第一个任务很简单,获取 Object
中字段的所有 Annotations
,同样递归地,已经完成。
(从百里香的 html5val 方言修改代码)
private List<Annotation> fieldAnnotations() {
Field field = this.fieldFinder.findField(this.targetClass, this.targetFieldName);
if (field != null) {
List<Annotation> annotations = Arrays.asList(field.getAnnotations());
List<Annotation> toAdd = new ArrayList<>();
for(Annotation a:annotations)
if(a.annotationType().isAssignableFrom(Valid.class)){
toAdd.addAll(new AnnotationExtractor(field.getType()).getAnnotationsForField(this.targetFieldName));
}
else
toAdd.add(a);
return toAdd;
}
return Collections.emptyList();
}
现在我正在尝试获取每个注释的国际化消息。
BeanPropertyBindingResult binding = new BeanPropertyBindingResult(realObject, root);
for (Annotation constraint : constraints) {
String message = AnnotationExtractor.getDefaultMessage(constraint);
binding.rejectValue(fieldName, constraint.annotationType().getSimpleName(), message);
}
List<ObjectError> errors = binding.getAllErrors();
RequestContext requestContext = (RequestContext) arguments.getContext().getVariables().get(SpringContextVariableNames.SPRING_REQUEST_CONTEXT);
for(ObjectError e:errors){
String s =requestContext.getMessage(e, true);
}
我正在收到国际化消息,如果 MessageSource
可以解决某些问题,那就太好了!
遗憾的是,我无法获取默认消息的消息,例如 org.hibernate.validator.constraints.Length.message
,但这不是问题(我总是可以通过我的 MessageSource
提供这些消息)。
为了让这个任务充分发挥作用,我错过了一件事,消息的参数。因此,已解决的消息看起来像这样。
Alias length must be between {2} and {1}
。 BeanPropertyBindingResult
有一种方法可以拒绝带有参数的值,但我不知道如何从注释中获取它。它可能是由 Validator
实施完成的,对吧?
它与 Spring DataBinder 对无效字段所做的工作相同,但我想在 HTML5 表单验证中获取自定义验证消息的消息。有谁知道如何通过 Spring 内部使用对象推送 bean 以获取这些消息?
还有一件重要的事情,一切都在 thymeleaf 上下文中(这是我对 html5val 方言的修改)
解决方案很难看。 完整的解决方案在我的 HTML5 Validation Dialect 分支中 https://bitbucket.org/globalbus/html5-validator-dialect-fork/commits/85da11b2c13dfbd90ff05a55014d792cb453d5e2
首先,我需要 SpringValidatorAdapter.getArgumentsForConstraints()
成为 public。
public class MyValidationAdapter extends SpringValidatorAdapter{
public MyValidationAdapter(Validator targetValidator) {
super(targetValidator);
}
@Override
public Object[] getArgumentsForConstraint(String objectName,
String field, ConstraintDescriptor<?> descriptor) {
return super.getArgumentsForConstraint(objectName, field, descriptor);
}
@Override
public void processConstraintViolations(
Set<ConstraintViolation<Object>> violations, Errors errors) {
super.processConstraintViolations(violations, errors);
}
}
我们需要向构造函数提供一个 Validator
对象。我们可以从传递给 Dialect
org.thymeleaf.Arguments
中获取它
this.requestContext = (RequestContext) this.arguments.getContext().getVariables().get(SpringContextVariableNames.SPRING_REQUEST_CONTEXT);
this.validatorFactory = this.requestContext.getWebApplicationContext().getBean(ValidatorFactory.class);
this.validator = new MyValidationAdapter(this.validatorFactory.getValidator());
现在,代码真的很乱
public void processField(Element fieldElement, String fieldName){
BeanPropertyBindingResult binding = new BeanPropertyBindingResult(
this.realObject, this.root);
PropertyDescriptor rootProp = this.beanDescriptor
.getConstraintsForProperty(fieldName);
List<PropertyDescriptor> finalProp = new LinkedList<PropertyDescriptor>();
if (rootProp.isCascaded())// if it's nested, scan all properties for
// annotation
finalProp.addAll(this.validator.getConstraintsForClass(
rootProp.getElementClass()).getConstrainedProperties());
else
finalProp.add(rootProp);
for (PropertyDescriptor prop : finalProp)
for (final ConstraintDescriptor<?> desc : prop
.getConstraintDescriptors()) {
Annotation constraint = desc.getAnnotation();
String className = this.beanDescriptor.getElementClass()
.getSimpleName();
className = Character.toLowerCase(className.charAt(0))
+ className.substring(1);
String field = className + "." + prop.getPropertyName();
String errorCode = constraint.annotationType().getSimpleName();
Object[] errorArgs = this.validator.getArgumentsForConstraint(
this.root, field, desc);
String message = (String) desc.getAttributes().get(ANNOTATION_MESSAGE);
if (INTERNAL.matcher(message).find())
message = this.validatorFactory.getMessageInterpolator().interpolate(
message, new Context() {
public Object getValidatedValue() {
return null;
}
public ConstraintDescriptor<?> getConstraintDescriptor() {
return desc;
}
});
String[] errorCodes = binding.resolveMessageCodes(errorCode,
field);
binding.addError(new FieldError(binding.getObjectName(), prop
.getPropertyName(), null, false, errorCodes, errorArgs,
message));
}
List<ObjectError> errors = binding.getAllErrors();
StringBuilder customValidation = new StringBuilder();
for (ObjectError e : errors) {
String s = this.requestContext.getMessage(e, true);
customValidation.append(s);
if (!s.endsWith("."))
customValidation.append(". ");
}
String onInvalid = String.format("this.setCustomValidity('%s')",
customValidation.toString());
fieldElement.setAttribute(ONINVALID_ATTR, onInvalid);
fieldElement.setAttribute(ONCHANGE_ATTR, "this.setCustomValidity('')");
}
线索在SpringValidatorAdapter.getArgumentsForConstraints()
,参数有效,可以为消息解析提供参数。
MessageInterpolator.interpolate()
可以提供内部 Hibernate 消息
RequestContext.getMessage()
.
这不是 "clean solution",我必须在更多用例中测试它。