spring SpringValidatorAdapter 源码
spring SpringValidatorAdapter 代码
文件路径:/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import java.util.Set;
import java.util.TreeMap;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ElementKind;
import jakarta.validation.Path;
import jakarta.validation.ValidationException;
import jakarta.validation.executable.ExecutableValidator;
import jakarta.validation.metadata.BeanDescriptor;
import jakarta.validation.metadata.ConstraintDescriptor;
import org.springframework.beans.NotReadablePropertyException;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.SmartValidator;
/**
* Adapter that takes a JSR-303 {@code javax.validator.Validator} and
* exposes it as a Spring {@link org.springframework.validation.Validator}
* while also exposing the original JSR-303 Validator interface itself.
*
* <p>Can be used as a programmatic wrapper. Also serves as base class for
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean},
* and as the primary implementation of the {@link SmartValidator} interface.
*
* <p>As of Spring Framework 5.0, this adapter is fully compatible with
* Bean Validation 1.1 as well as 2.0.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
* @see SmartValidator
* @see CustomValidatorBean
* @see LocalValidatorFactoryBean
*/
public class SpringValidatorAdapter implements SmartValidator, jakarta.validation.Validator {
private static final Set<String> internalAnnotationAttributes = new HashSet<>(4);
static {
internalAnnotationAttributes.add("message");
internalAnnotationAttributes.add("groups");
internalAnnotationAttributes.add("payload");
}
@Nullable
private jakarta.validation.Validator targetValidator;
/**
* Create a new SpringValidatorAdapter for the given JSR-303 Validator.
* @param targetValidator the JSR-303 Validator to wrap
*/
public SpringValidatorAdapter(jakarta.validation.Validator targetValidator) {
Assert.notNull(targetValidator, "Target Validator must not be null");
this.targetValidator = targetValidator;
}
SpringValidatorAdapter() {
}
void setTargetValidator(jakarta.validation.Validator targetValidator) {
this.targetValidator = targetValidator;
}
//---------------------------------------------------------------------
// Implementation of Spring Validator interface
//---------------------------------------------------------------------
@Override
public boolean supports(Class<?> clazz) {
return (this.targetValidator != null);
}
@Override
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void validateValue(
Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validateValue(
(Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
}
}
/**
* Turn the specified validation hints into JSR-303 validation groups.
* @since 5.1
*/
private Class<?>[] asValidationGroups(Object... validationHints) {
Set<Class<?>> groups = new LinkedHashSet<>(4);
for (Object hint : validationHints) {
if (hint instanceof Class<?> clazz) {
groups.add(clazz);
}
}
return ClassUtils.toClassArray(groups);
}
/**
* Process the given JSR-303 ConstraintViolations, adding corresponding errors to
* the provided Spring {@link Errors} object.
* @param violations the JSR-303 ConstraintViolation results
* @param errors the Spring errors object to register to
*/
@SuppressWarnings("serial")
protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
for (ConstraintViolation<Object> violation : violations) {
String field = determineField(violation);
FieldError fieldError = errors.getFieldError(field);
if (fieldError == null || !fieldError.isBindingFailure()) {
try {
ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
String errorCode = determineErrorCode(cd);
Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
if (errors instanceof BindingResult bindingResult) {
// Can do custom FieldError registration with invalid value from ConstraintViolation,
// as necessary for Hibernate Validator compatibility (non-indexed set path in field)
String nestedField = bindingResult.getNestedPath() + field;
if (nestedField.isEmpty()) {
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
ObjectError error = new ViolationObjectError(
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,
rejectedValue, errorCodes, errorArgs, violation, this);
bindingResult.addError(error);
}
}
else {
// Got no BindingResult - can only do standard rejectValue call
// with automatic extraction of the current field value
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
}
}
catch (NotReadablePropertyException ex) {
throw new IllegalStateException("JSR-303 validated property '" + field +
"' does not have a corresponding accessor for Spring data binding - " +
"check your DataBinder's configuration (bean property versus direct field access)", ex);
}
}
}
}
/**
* Determine a field for the given constraint violation.
* <p>The default implementation returns the stringified property path.
* @param violation the current JSR-303 ConstraintViolation
* @return the Spring-reported field (for use with {@link Errors})
* @since 4.2
* @see jakarta.validation.ConstraintViolation#getPropertyPath()
* @see org.springframework.validation.FieldError#getField()
*/
protected String determineField(ConstraintViolation<Object> violation) {
Path path = violation.getPropertyPath();
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Path.Node node : path) {
if (node.isInIterable()) {
sb.append('[');
Object index = node.getIndex();
if (index == null) {
index = node.getKey();
}
if (index != null) {
sb.append(index);
}
sb.append(']');
}
String name = node.getName();
if (name != null && node.getKind() == ElementKind.PROPERTY && !name.startsWith("<")) {
if (!first) {
sb.append('.');
}
first = false;
sb.append(name);
}
}
return sb.toString();
}
/**
* Determine a Spring-reported error code for the given constraint descriptor.
* <p>The default implementation returns the simple class name of the descriptor's
* annotation type. Note that the configured
* {@link org.springframework.validation.MessageCodesResolver} will automatically
* generate error code variations which include the object name and the field name.
* @param descriptor the JSR-303 ConstraintDescriptor for the current violation
* @return a corresponding error code (for use with {@link Errors})
* @since 4.2
* @see jakarta.validation.metadata.ConstraintDescriptor#getAnnotation()
* @see org.springframework.validation.MessageCodesResolver
*/
protected String determineErrorCode(ConstraintDescriptor<?> descriptor) {
return descriptor.getAnnotation().annotationType().getSimpleName();
}
/**
* Return FieldError arguments for a validation error on the given field.
* Invoked for each violated constraint.
* <p>The default implementation returns a first argument indicating the field name
* (see {@link #getResolvableField}). Afterwards, it adds all actual constraint
* annotation attributes (i.e. excluding "message", "groups" and "payload") in
* alphabetical order of their attribute names.
* <p>Can be overridden to e.g. add further attributes from the constraint descriptor.
* @param objectName the name of the target object
* @param field the field that caused the binding error
* @param descriptor the JSR-303 constraint descriptor
* @return the Object array that represents the FieldError arguments
* @see org.springframework.validation.FieldError#getArguments
* @see org.springframework.context.support.DefaultMessageSourceResolvable
* @see org.springframework.validation.DefaultBindingErrorProcessor#getArgumentsForBindError
*/
protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor<?> descriptor) {
List<Object> arguments = new ArrayList<>();
arguments.add(getResolvableField(objectName, field));
// 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 (attributeValue instanceof String str) {
attributeValue = new ResolvableAttribute(str);
}
attributesToExpose.put(attributeName, attributeValue);
}
});
arguments.addAll(attributesToExpose.values());
return arguments.toArray();
}
/**
* Build a resolvable wrapper for the specified field, allowing to resolve the field's
* name in a {@code MessageSource}.
* <p>The default implementation returns a first argument indicating the field:
* of type {@code DefaultMessageSourceResolvable}, with "objectName.field" and "field"
* as codes, and with the plain field name as default message.
* @param objectName the name of the target object
* @param field the field that caused the binding error
* @return a corresponding {@code MessageSourceResolvable} for the specified field
* @since 4.3
* @see #getArgumentsForConstraint
*/
protected MessageSourceResolvable getResolvableField(String objectName, String field) {
String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
return new DefaultMessageSourceResolvable(codes, field);
}
/**
* Extract the rejected value behind the given constraint violation,
* for exposure through the Spring errors representation.
* @param field the field that caused the binding error
* @param violation the corresponding JSR-303 ConstraintViolation
* @param bindingResult a Spring BindingResult for the backing object
* which contains the current field's value
* @return the invalid value to expose as part of the field error
* @since 4.2
* @see jakarta.validation.ConstraintViolation#getInvalidValue()
* @see org.springframework.validation.FieldError#getRejectedValue()
*/
@Nullable
protected Object getRejectedValue(String field, ConstraintViolation<Object> violation, BindingResult bindingResult) {
Object invalidValue = violation.getInvalidValue();
if (!field.isEmpty() && !field.contains("[]") &&
(invalidValue == violation.getLeafBean() || field.contains("[") || field.contains("."))) {
// Possibly a bean constraint with property path: retrieve the actual property value.
// However, explicitly avoid this for "address[]" style paths that we can't handle.
invalidValue = bindingResult.getRawFieldValue(field);
}
return invalidValue;
}
/**
* Indicate whether this violation's interpolated message has remaining
* placeholders and therefore requires {@link java.text.MessageFormat}
* to be applied to it. Called for a Bean Validation defined message
* (coming out {@code ValidationMessages.properties}) when rendered
* as the default message in Spring's MessageSource.
* <p>The default implementation considers a Spring-style "{0}" placeholder
* for the field name as an indication for {@link java.text.MessageFormat}.
* Any other placeholder or escape syntax occurrences are typically a
* mismatch, coming out of regex pattern values or the like. Note that
* standard Bean Validation does not support "{0}" style placeholders at all;
* this is a feature typically used in Spring MessageSource resource bundles.
* @param violation the Bean Validation constraint violation, including
* BV-defined interpolation of named attribute references in its message
* @return {@code true} if {@code java.text.MessageFormat} is to be applied,
* or {@code false} if the violation's message should be used as-is
* @since 5.1.8
* @see #getArgumentsForConstraint
*/
protected boolean requiresMessageFormat(ConstraintViolation<?> violation) {
return containsSpringStylePlaceholder(violation.getMessage());
}
private static boolean containsSpringStylePlaceholder(@Nullable String message) {
return (message != null && message.contains("{0}"));
}
//---------------------------------------------------------------------
// Implementation of JSR-303 Validator interface
//---------------------------------------------------------------------
@Override
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validate(object, groups);
}
@Override
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateProperty(object, propertyName, groups);
}
@Override
public <T> Set<ConstraintViolation<T>> validateValue(
Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateValue(beanType, propertyName, value, groups);
}
@Override
public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.getConstraintsForClass(clazz);
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(@Nullable Class<T> type) {
Assert.state(this.targetValidator != null, "No target Validator set");
try {
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
}
catch (ValidationException ex) {
// Ignore if just being asked for plain JSR-303 Validator
if (jakarta.validation.Validator.class == type) {
return (T) this.targetValidator;
}
throw ex;
}
}
@Override
public ExecutableValidator forExecutables() {
Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.forExecutables();
}
/**
* Wrapper for a String attribute which can be resolved via a {@code MessageSource},
* falling back to the original attribute as a default value otherwise.
*/
@SuppressWarnings("serial")
private static class ResolvableAttribute implements MessageSourceResolvable, Serializable {
private final String resolvableString;
public ResolvableAttribute(String resolvableString) {
this.resolvableString = resolvableString;
}
@Override
public String[] getCodes() {
return new String[] {this.resolvableString};
}
@Override
@Nullable
public Object[] getArguments() {
return null;
}
@Override
public String getDefaultMessage() {
return this.resolvableString;
}
@Override
public String toString() {
return this.resolvableString;
}
}
/**
* Subclass of {@code ObjectError} with Spring-style default message rendering.
*/
@SuppressWarnings("serial")
private static class ViolationObjectError extends ObjectError implements Serializable {
@Nullable
private transient SpringValidatorAdapter adapter;
@Nullable
private transient ConstraintViolation<?> violation;
public ViolationObjectError(String objectName, String[] codes, Object[] arguments,
ConstraintViolation<?> violation, SpringValidatorAdapter adapter) {
super(objectName, codes, arguments, violation.getMessage());
this.adapter = adapter;
this.violation = violation;
wrap(violation);
}
@Override
public boolean shouldRenderDefaultMessage() {
return (this.adapter != null && this.violation != null ?
this.adapter.requiresMessageFormat(this.violation) :
containsSpringStylePlaceholder(getDefaultMessage()));
}
}
/**
* Subclass of {@code FieldError} with Spring-style default message rendering.
*/
@SuppressWarnings("serial")
private static class ViolationFieldError extends FieldError implements Serializable {
@Nullable
private transient SpringValidatorAdapter adapter;
@Nullable
private transient ConstraintViolation<?> violation;
public ViolationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes,
Object[] arguments, ConstraintViolation<?> violation, SpringValidatorAdapter adapter) {
super(objectName, field, rejectedValue, false, codes, arguments, violation.getMessage());
this.adapter = adapter;
this.violation = violation;
wrap(violation);
}
@Override
public boolean shouldRenderDefaultMessage() {
return (this.adapter != null && this.violation != null ?
this.adapter.requiresMessageFormat(this.violation) :
containsSpringStylePlaceholder(getDefaultMessage()));
}
}
}
相关信息
相关文章
spring BeanValidationPostProcessor 源码
spring LocalValidatorFactoryBean 源码
spring LocaleContextMessageInterpolator 源码
spring MessageSourceResourceBundleLocator 源码
spring MethodValidationInterceptor 源码
spring MethodValidationPostProcessor 源码
spring OptionalValidatorFactoryBean 源码
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦