Here is an example
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Annotation {
}
@Configuration
public class Configuration {
@Bean
@Annotation
public Test getTest() {
return new Test();
}
}
public class Test() {
public void test() {
// how can get the annotation `@Annotation` here?
}
}
Here is what I have tried getClass().getAnnotations()
but this returns empty array. I can see why since getClass()
return Test.class which does not have the annotation. How can I get the method that creates this instance and then get the annotation?
CodePudding user response:
You could, in theory, inspect the current Thread stack to determine the name of your caller, then look up the class definition, locate the method, and read its annotations:
var t = Thread.currentThread().getStackTrace()[2];
var className = t.getClassName();
Class<?> clazz;
try {
clazz = Test.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Caller was loaded by a different ClassLoader :-(");
}
for (var method : clazz.getDeclaredMethods()) {
if (method.getName().equals(t.getMethodName())) {
return method.getAnnotation(YourAnnotation.class).value();
}
}
throw new RuntimeException("Method not found - I might have found the wrong class definition");
However:
- inspecting the stack is rather slow, in particular if the stack is deep
- inspecting the stack the stack is brittle with respect to refactorings (people don't expect that factoring out code into a utility method will change behaviour)
- the compiler can not check that the caller provides the required annotation
- this only works reliably if all code is loaded by the same
ClassLoader
- this can not distinguish overloaded methods
This is therefore a rather brittle hack. Are you sure that there is no better option? For instance, requiring the caller to pass the value as a method parameter would have none of these shortcomings ...
CodePudding user response:
You can use ConfigurableListableBeanFactory
to get metadata about any bean.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface CustomAnnotation {
}
@org.springframework.context.annotation.Configuration
public static class ContextConfiguration {
@Bean(name = "TEST")
@CustomAnnotation
public TestObject getTest() {
return new TestObject();
}
}
public class TestObject {
@Autowired
ConfigurableListableBeanFactory beanFactory;
public void test() {
CustomAnnotation customAnnotation = (CustomAnnotation) getBeanAnnotation("TEST", CustomAnnotation.class);
}
private Annotation getBeanAnnotation(String beanName, java.lang.Class<? extends Annotation> clazz) {
Annotation annotation = null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if( beanDefinition != null && beanDefinition.getSource() instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) beanDefinition.getSource();
annotation = Arrays.stream(metadata.getIntrospectedMethod().getDeclaredAnnotations()).filter(annot -> annot.annotationType().equals(clazz)).findFirst().orElse(null);
}
return annotation;
}
}