Improve SpringValidatorAdapter and MethodValidationAdapter performance

Closes gh-36621

(cherry picked from commit b8f1005897)
This commit is contained in:
Juergen Hoeller
2026-04-08 13:39:20 +02:00
parent 2801c7e23e
commit d675132ac0
2 changed files with 28 additions and 11 deletions
@@ -553,7 +553,7 @@ public class MethodValidationAdapter implements MethodValidator {
@SuppressWarnings("serial")
private static class ViolationMessageSourceResolvable extends DefaultMessageSourceResolvable {
private class ViolationMessageSourceResolvable extends DefaultMessageSourceResolvable {
private final transient ConstraintViolation<Object> violation;
@@ -567,6 +567,11 @@ public class MethodValidationAdapter implements MethodValidator {
public ConstraintViolation<Object> getViolation() {
return this.violation;
}
@Override
public boolean shouldRenderDefaultMessage() {
return validatorAdapter.get().requiresMessageFormat(this.violation);
}
}
@@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -64,7 +65,7 @@ import org.springframework.validation.SmartValidator;
*/
public class SpringValidatorAdapter implements SmartValidator, jakarta.validation.Validator {
private static final Set<String> internalAnnotationAttributes = Set.of("message", "groups", "payload");
private static final Set<String> INTERNAL_ANNOTATION_ATTRIBUTES = Set.of("message", "groups", "payload");
@Nullable
@@ -145,10 +146,21 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
*/
@SuppressWarnings("serial")
protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
// Pre-identify binding failures (using getAllErrors to avoid intermediate list)
Set<String> failedFields = new HashSet<>();
if (errors.hasErrors()) {
for (ObjectError error : errors.getAllErrors()) {
if (error instanceof FieldError fieldError && fieldError.isBindingFailure()) {
failedFields.add(fieldError.getField());
}
}
}
// Turn the ConstraintViolations into Object/FieldErrors,
// but only for fields where binding has been successful.
for (ConstraintViolation<Object> violation : violations) {
String field = determineField(violation);
FieldError fieldError = errors.getFieldError(field);
if (fieldError == null || !fieldError.isBindingFailure()) {
if (!failedFields.contains(field)) {
try {
ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
String errorCode = determineErrorCode(cd);
@@ -159,14 +171,14 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
String nestedField = bindingResult.getNestedPath() + field;
if (nestedField.isEmpty()) {
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
ObjectError error = new ViolationObjectError(
ObjectError error = new ValidationObjectError(
errors.getObjectName(), errorCodes, errorArgs, violation, this);
bindingResult.addError(error);
}
else {
Object rejectedValue = getRejectedValue(field, violation, bindingResult);
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
FieldError error = new ViolationFieldError(errors.getObjectName(), nestedField,
FieldError error = new ValidationFieldError(errors.getObjectName(), nestedField,
rejectedValue, errorCodes, errorArgs, violation, this);
bindingResult.addError(error);
}
@@ -261,7 +273,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
// Using a TreeMap for alphabetical ordering of attribute names
Map<String, Object> attributesToExpose = new TreeMap<>();
descriptor.getAttributes().forEach((attributeName, attributeValue) -> {
if (!internalAnnotationAttributes.contains(attributeName)) {
if (!INTERNAL_ANNOTATION_ATTRIBUTES.contains(attributeName)) {
if (attributeValue instanceof String str) {
attributeValue = new ResolvableAttribute(str);
}
@@ -442,7 +454,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
* Subclass of {@code ObjectError} with Spring-style default message rendering.
*/
@SuppressWarnings("serial")
private static class ViolationObjectError extends ObjectError implements Serializable {
private static class ValidationObjectError extends ObjectError implements Serializable {
@Nullable
private transient SpringValidatorAdapter adapter;
@@ -450,7 +462,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
@Nullable
private transient ConstraintViolation<?> violation;
public ViolationObjectError(String objectName, String[] codes, Object[] arguments,
public ValidationObjectError(String objectName, String[] codes, Object[] arguments,
ConstraintViolation<?> violation, SpringValidatorAdapter adapter) {
super(objectName, codes, arguments, violation.getMessage());
@@ -472,7 +484,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
* Subclass of {@code FieldError} with Spring-style default message rendering.
*/
@SuppressWarnings("serial")
private static class ViolationFieldError extends FieldError implements Serializable {
private static class ValidationFieldError extends FieldError implements Serializable {
@Nullable
private transient SpringValidatorAdapter adapter;
@@ -480,7 +492,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio
@Nullable
private transient ConstraintViolation<?> violation;
public ViolationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes,
public ValidationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes,
Object[] arguments, ConstraintViolation<?> violation, SpringValidatorAdapter adapter) {
super(objectName, field, rejectedValue, false, codes, arguments, violation.getMessage());