diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationMergedAnnotationsTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationMergedAnnotationsTests.java new file mode 100644 index 0000000000..2f763f1a27 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationMergedAnnotationsTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2022 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.lang.reflect.Method; + +import org.hibernate.validator.constraints.URL; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import org.springframework.core.annotation.MergedAnnotations; + +/** + * Smoke tests specific to the combination of {@link MergedAnnotations} + * and Bean Validation constraint annotations. + * + * @author Sam Brannen + * @since 6.0 + */ +class BeanValidationMergedAnnotationsTests { + + @Disabled("This test should only be run manually to inspect log messages") + @Test + @URL + void constraintAnnotationOnMethod() throws Exception { + // If the issue raised in gh-29206 had not been addressed, we would see + // a log message similar to the following. + // 19:07:33.848 [main] WARN o.s.c.a.AnnotationTypeMapping - Support for + // convention-based annotation attribute overrides is deprecated and will + // be removed in Spring Framework 6.1. Please annotate the following attributes + // in @org.hibernate.validator.constraints.URL with appropriate @AliasFor + // declarations: [regexp, payload, flags, groups, message] + Method method = getClass().getDeclaredMethod("constraintAnnotationOnMethod"); + MergedAnnotations mergedAnnotations = MergedAnnotations.from(method); + mergedAnnotations.get(URL.class); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java index 5007729830..f39b324026 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,9 +53,15 @@ final class AnnotationTypeMapping { private static final Log logger = LogFactory.getLog(AnnotationTypeMapping.class); + private static final Predicate isBeanValidationConstraint = annotation -> + annotation.annotationType().getName().equals("jakarta.validation.Constraint"); + /** - * Set of fully qualified class names for annotations which we have already - * checked for use of convention-based annotation attribute overrides. + * Set used to track which convention-based annotation attribute overrides + * have already been checked. Each key is the combination of the fully + * qualified class names of a composed annotation and a meta-annotation + * that it is either present or meta-present on the composed annotation, + * separated by a dash. * @since 6.0 * @see #addConventionMappings() */ @@ -299,9 +306,18 @@ final class AnnotationTypeMapping { } } String rootAnnotationTypeName = this.root.annotationType.getName(); - // We want to avoid duplicate log warnings as much as possible, without full synchronization. - if (conventionBasedOverrideCheckCache.add(rootAnnotationTypeName) && - !conventionMappedAttributes.isEmpty() && logger.isWarnEnabled()) { + String cacheKey = rootAnnotationTypeName + '-' + this.annotationType.getName(); + // We want to avoid duplicate log warnings as much as possible, without full synchronization, + // and we intentionally invoke add() before checking if any convention-based overrides were + // actually encountered in order to ensure that we add a "tracked" entry for the current cache + // key in any case. + // In addition, we do NOT want to log warnings for custom Java Bean Validation constraint + // annotations that are meta-annotated with other constraint annotations -- for example, + // @org.hibernate.validator.constraints.URL which overrides attributes in + // @jakarta.validation.constraints.Pattern. + if (conventionBasedOverrideCheckCache.add(cacheKey) && !conventionMappedAttributes.isEmpty() && + Arrays.stream(this.annotationType.getAnnotations()).noneMatch(isBeanValidationConstraint) && + logger.isWarnEnabled()) { logger.warn(""" Support for convention-based annotation attribute overrides is deprecated \ and will be removed in Spring Framework 6.1. Please annotate the following \