Home > Mobile >  Get the annotation on method from the instance that the method created
Get the annotation on method from the instance that the method created

Time:03-04

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;
    }

}
  • Related