Overview
In Spring 5, AnnotationUtils.findAnnotation(Class<?>, Class<A>)
would find the annotation on the following class: Object foo = new @MyAnnotation Object() {};
-- for example, AnnotationUtils.findAnnotation(foo, MyAnnotation.class)
would not return null.
With Spring 6.0.0-M5 / Java 17 the annotation will no longer be found.
See gh-28895
Comment From: sbrannen
Hi @janeisklar,
I ran the following example application against Spring Framework 6.0.x, 5.3.x, 5.2.x, 5.1.x, and 5.0.0, and it fails every time.
For Spring 6.0.x, I ran against JDK 17.0.8+9-LTS-211.
For the Spring 5.x variants, I ran against JDK 1.8.0_362-b09.
package example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
public class TestMain {
public static void main(String[] args) {
Object object = new @TypeUseAnnotation Object() {};
Class<?> clazz = object.getClass();
TypeUseAnnotation annotation = AnnotationUtils.findAnnotation(clazz, TypeUseAnnotation.class);
Assert.state(annotation != null, "annotation is null");
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeUseAnnotation {
}
}
Are you certain that your tests passed on Spring Framework 5.x and JDK 8?
If so, which exact versions of Spring and the JDK did you test against?
In the interim, I am closing this PR in favor of #31360.
Unless I'm mistaken, Spring Framework has never provided explicit/intentional support for finding TYPE_USE
annotations.
Though, if you can provide a working example to prove otherwise, please do.
Thanks,
Sam
Comment From: sbrannen
- superseded by #31360
Comment From: janeisklar
Hi @sbrannen,
I did not notice that before, but it seems to be important that you add Element.TYPE
in addition to Element.TYPE_USE
to your annotation.
The following test passes on my system:
$ java -version
openjdk version "1.8.0_382"
OpenJDK Runtime Environment (build 1.8.0_382-8u382-ga-1~22.04.1-b05)
OpenJDK 64-Bit Server VM (build 25.382-b05, mixed mode)
package com.foo;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
class ReproducerTest {
@Test
void findsAnnotationOnAnnotatedObject() {
final Object a = new @TypeAnnotation Object() {};
assertNotNull(AnnotationUtils.findAnnotation(a.getClass(), TypeAnnotation.class));
}
@Test
void doesNotFindAnnotationOnAnnotatedObject() {
final Object a = new @TypeUseAnnotation Object() {};
TypeUseAnnotation annotation = AnnotationUtils.findAnnotation(a.getClass(), TypeUseAnnotation.class);
assertNull(annotation);
}
@Target({ ElementType.TYPE, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface TypeAnnotation {}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeUseAnnotation {}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>type-use-bug-reproducer</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Thank you for looking into this!
Best, André
Comment From: sbrannen
Thanks for the feedback, @janeisklar.
I did not notice that before, but it seems to be important that you add
Element.TYPE
in addition toElement.TYPE_USE
to your annotation.
That was the missing piece of the puzzle.
After further investigation, I've determined that your use case was never intended to work.
Spring uses Class#getDeclaredAnnotations
to find annotations in its search algorithms, and that method should never have returned TYPE_USE
annotations. Instead, TYPE_USE
annotations should only be returned via the AnnotatedType
APIs introduced in Java 8 alongside support for TYPE_USE
annotations.
java.lang.Class.getAnnotatedSuperclass()
java.lang.Class.getAnnotatedInterfaces()
There were apparently changes made in Java 12 that fixed the bug that your code relied on (though, I was not able to locate the specific issue for that change).
Consequently, your test passes on Java 8-11 but fails with Java 12+.
Furthermore, all of the traditional methods that Spring has been using for years (like Class#getDeclaredAnnotations
) have the following "disclaimer" in their Javadoc since Java 15.
Note that any annotations returned by this method are declaration annotations.
Similarly, methods such as java.lang.reflect.AnnotatedType.getDeclaredAnnotations()
have the following "disclaimer" in their Javadoc since Java 15.
Note that any annotations returned by this method are type annotations.
In light of that, I have changed the label for this issue to invalid
; however, we will consider introducing support for TYPE_USE
annotations in #31360.
Comment From: janeisklar
Thank you for the insights, @sbrannen.
In case it helps: I have been using this ~~feature~~bug to create beans in a @TestConfiguration
class that needed to be annotated with some custom annotations.
It's not a feature that one's life depends on, but it can be quite handy.
Comment From: sbrannen
Thank you for the insights, @sbrannen.
You're welcome!
In case it helps: I have been using this ~feature~bug to create beans in a
@TestConfiguration
class that needed to be annotated with some custom annotations.
Ahhh, OK. Thanks for providing the use case.
FWIW, if that's your goal, you can achieve the same via a "local class" instead of an "anonymous class" as follows.
class AnnotatedLocalClassTests {
@Test
void test() {
@TypeUseAnnotation
class AnnotatedObject extends Object /* or some bean/component type */ {
};
Object object = new AnnotatedObject();
Class<?> clazz = object.getClass();
TypeUseAnnotation annotation = AnnotationUtils.findAnnotation(clazz, TypeUseAnnotation.class);
assertThat(annotation).isNotNull();
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeUseAnnotation {
}
}