Отразить аннотацию экземпляра моего собственного класса

#java #reflection #annotations #runtime #java-annotations

#java #отражение #аннотации #время выполнения #java-аннотации

Вопрос:

У меня есть несколько классов A1, A2, A3, которые расширяют абстрактный класс myA. Эти классы имеют x полей класса B. Поля класса B аннотируются с помощью теста аннотации.(Тест доступен во время выполнения) Как я могу получить тест аннотации и его значение изнутри метода класса B.

 public class A1 extends myA{

    @Test("all")
    private B b1;

    @Test("none")
    private B b2;

    @Test("none")
    private B b3;

    //....

    public void interact(){
        b2.doSomethingBasedOnMyAnnotation();
    }

}
public class A2 extends myA{

    @Test("none")
    private B b;

    //....
}

public class B{

     public void doSomethingBasedOnMyAnnotation(){
        // How to reach the Annotation from here ?
     }

}

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Test{
    String value() default "all";
}
  

Ответ №1:

Когда вы помещаете аннотацию в переменную, она становится статическим свойством этой переменной, а не объекта, к которому вы можете получить доступ через эту переменную. Рассмотрим:

 public class A1 extends myA{

    @Test("all")
    private B b1=new B();

    @Test("none")
    private B b2=b1;

    //....

    public void interact(){
        // this
        b2.doSomethingBasedOnMyAnnotation();
        // is exactly the same as
        b1.doSomethingBasedOnMyAnnotation();
    }
}
  

Неверно даже предполагать, что задействована аннотированная переменная. Как насчет
new B().doSomethingBasedOnMyAnnotation() ?

Поскольку поля разрешаются во время компиляции, в любом случае в вашей желаемой операции нет абстракции. Если вы знаете, что собираетесь вызывать, b2.doSomethingBasedOnMyAnnotation(); , вы уже знаете, какое поле вы используете, и нет проблем с предоставлением значения аннотации b2 в качестве параметра вызываемому методу, вместо того, чтобы ожидать, что получатель волшебным образом узнает. Например.

 public class B{
    public void doSomething(String arg){
    }
}
public class A1 extends myA{

    @Test("all")
    private B b1;

    @Test("none")
    private B b2;

    //....

    public void interact(){
        b1.doSomething(get("b1"));
        b2.doSomething(get("b2"));
    }
    static String get(String fieldName) {
        try {
            return A1.class.getDeclaredField(fieldName)
                .getAnnotation(Test.class).value();
        } catch(NoSuchFieldException ex) {
            throw new IllegalStateException(ex);
        }
    }
}
  

хотя мы могли бы с радостью работать без отражения:

 public class A1 extends myA{

    static final String TEST_B1="all";

    @Test(TEST_B1)
    private B b1;

    static final String TEST_B2="none";

    @Test(TEST_B2)
    private B b2;

    static final String TEST_B3="none";

    @Test(TEST_B3)
    private B b3;

    //....

    public void interact(){
        b1.doSomething(TEST_B1);
        b2.doSomething(TEST_B2);
    }
}
  

Если вы хотите убедиться, что вызывающий объект не сможет случайно передать неправильный аргумент, вместо этого используйте инкапсуляцию:

 public final class EncapsulatedB {
    final String testValue;
    B b;
    EncapsulatedB(String value) {
        this(value, null);
    }
    EncapsulatedB(String value, B initialB) {
        testValue=value;
        b=initialB;
    }
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
    public void doSomething() {
        b.doSomething(testValue);
    }
}

public class A1 extends myA{
    private final EncapsulatedB b1=new EncapsulatedB("all");
    private final EncapsulatedB b2=new EncapsulatedB("none");
    private final EncapsulatedB b3=new EncapsulatedB("none");

    //....

    public void interact(){
        b1.doSomething();
        b2.doSomething();
    }
}