Browse Source

Log warning if meta-annotation is ignored due to types not present

Prior to this commit, if a meta-annotation could not be loaded because
its attributes referenced types not present in the classpath, the
meta-annotation was silently ignored.

To improve diagnostics for such use cases, this commit introduces WARN
support in IntrospectionFailureLogger and revises AttributeMethods.canLoad()
to log a warning if a meta-annotation is ignored due to an exception
thrown while attempting to load its attributes.

For example, a warning similar to the following is now logged in such
cases.

WARN o.s.c.a.MergedAnnotation -
  Failed to introspect meta-annotation @example.MyAnnotation on class
  example.Config: java.lang.TypeNotPresentException:
  Type example.OptionalDependency not present

This commit also improves log messages in AnnotationTypeMappings.

Closes gh-35927
pull/34532/merge
Sam Brannen 2 weeks ago
parent
commit
62d09be2ae
  1. 10
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java
  2. 2
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java
  3. 10
      spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java
  4. 16
      spring-core/src/main/java/org/springframework/core/annotation/IntrospectionFailureLogger.java
  5. 4
      spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java

10
spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java

@ -79,7 +79,7 @@ final class AnnotationTypeMappings { @@ -79,7 +79,7 @@ final class AnnotationTypeMappings {
private void addAllMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
addIfPossible(queue, null, annotationType, null, false, visitedAnnotationTypes);
while (!queue.isEmpty()) {
AnnotationTypeMapping mapping = queue.removeFirst();
this.mappings.add(mapping);
@ -109,12 +109,12 @@ final class AnnotationTypeMappings { @@ -109,12 +109,12 @@ final class AnnotationTypeMappings {
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {
addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
addIfPossible(queue, source, ann.annotationType(), ann, true, new HashSet<>());
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation ann,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
boolean meta, Set<Class<? extends Annotation>> visitedAnnotationTypes) {
try {
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));
@ -122,8 +122,8 @@ final class AnnotationTypeMappings { @@ -122,8 +122,8 @@ final class AnnotationTypeMappings {
catch (Exception ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
if (failureLogger.isEnabled()) {
failureLogger.log("Failed to introspect meta-annotation " + annotationType.getName(),
(source != null ? source.getAnnotationType() : null), ex);
failureLogger.log("Failed to introspect " + (meta ? "meta-annotation @" : "annotation @") +
annotationType.getName(), (source != null ? source.getAnnotationType() : null), ex);
}
}
}

2
spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java

@ -443,7 +443,7 @@ abstract class AnnotationsScanner { @@ -443,7 +443,7 @@ abstract class AnnotationsScanner {
Annotation annotation = annotations[i];
//noinspection DataFlowIssue
if (isIgnorable(annotation.annotationType()) ||
!AttributeMethods.forAnnotationType(annotation.annotationType()).canLoad(annotation)) {
!AttributeMethods.forAnnotationType(annotation.annotationType()).canLoad(annotation, source)) {
annotations[i] = null;
}
else {

10
spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
@ -38,6 +39,8 @@ import org.springframework.util.ReflectionUtils; @@ -38,6 +39,8 @@ import org.springframework.util.ReflectionUtils;
*/
final class AttributeMethods {
private static final IntrospectionFailureLogger failureLogger = IntrospectionFailureLogger.WARN;
static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);
static final Map<Class<? extends Annotation>, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();
@ -91,10 +94,11 @@ final class AttributeMethods { @@ -91,10 +94,11 @@ final class AttributeMethods {
* exceptions for {@code Class} values (instead of the more typical early
* {@code Class.getAnnotations() failure} on a regular JVM).
* @param annotation the annotation to check
* @param source the element that the supplied annotation is declared on
* @return {@code true} if all values are present
* @see #validate(Annotation)
*/
boolean canLoad(Annotation annotation) {
boolean canLoad(Annotation annotation, AnnotatedElement source) {
assertAnnotation(annotation);
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
@ -107,6 +111,10 @@ final class AttributeMethods { @@ -107,6 +111,10 @@ final class AttributeMethods {
}
catch (Throwable ex) {
// TypeNotPresentException etc. -> annotation type not actually loadable.
if (failureLogger.isEnabled()) {
failureLogger.log("Failed to introspect meta-annotation @" +
getName(annotation.annotationType()), source, ex);
}
return false;
}
}

16
spring-core/src/main/java/org/springframework/core/annotation/IntrospectionFailureLogger.java

@ -24,9 +24,10 @@ import org.jspecify.annotations.Nullable; @@ -24,9 +24,10 @@ import org.jspecify.annotations.Nullable;
* Log facade used to handle annotation introspection failures (in particular
* {@code TypeNotPresentExceptions}). Allows annotation processing to continue,
* assuming that when Class attribute values are not resolvable the annotation
* should effectively disappear.
* should effectively be ignored.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
enum IntrospectionFailureLogger {
@ -51,13 +52,24 @@ enum IntrospectionFailureLogger { @@ -51,13 +52,24 @@ enum IntrospectionFailureLogger {
public void log(String message) {
getLogger().info(message);
}
},
WARN {
@Override
public boolean isEnabled() {
return getLogger().isWarnEnabled();
}
@Override
public void log(String message) {
getLogger().warn(message);
}
};
private static @Nullable Log logger;
void log(String message, @Nullable Object source, Exception ex) {
void log(String message, @Nullable Object source, Throwable ex) {
String on = (source != null ? " on " + source : "");
log(message + on + ": " + ex);
}

4
spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java

@ -112,7 +112,7 @@ class AttributeMethodsTests { @@ -112,7 +112,7 @@ class AttributeMethodsTests {
ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willThrow(TypeNotPresentException.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
assertThat(attributes.canLoad(annotation)).isFalse();
assertThat(attributes.canLoad(annotation, getClass())).isFalse();
}
@Test
@ -121,7 +121,7 @@ class AttributeMethodsTests { @@ -121,7 +121,7 @@ class AttributeMethodsTests {
ClassValue annotation = mock();
given(annotation.value()).willReturn((Class) InputStream.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
assertThat(attributes.canLoad(annotation)).isTrue();
assertThat(attributes.canLoad(annotation, getClass())).isTrue();
}
@Test

Loading…
Cancel
Save