Как получить аннотацию из переопределенного метода в Java?

у меня следующая ситуация

class Parent {
    @SomeAnnotation(someValue)
    public void someMethod(){...}
}

class Child extends Parent {
    @Override
    public void someMethod(){...}
}

и мне нужно сделать @SomeAnnotation когда у меня есть ссылка на метод Child.someMethod.

используя Child.getSuperclass() Я могу сделать Parent.class. Кроме того, я нашел решение здесь получить ссылку на MethodHandle of Parent.someMethod, так что у меня есть

MethodHandle parentMethodHandle = MethodHandles.lookup().findSpecial(
        Parent.class, childSomeMethod.getName(),
        MethodType.methodType(Void.class), Child.class);

однако, я не могу найти способ, чтобы получить ссылку на метод Parent.someMethod С parentMethodHandle to getAnnotation() от него. Пожалуйста помочь.

4 ответов


вот полное решение. Я считаю, что это должно быть правильно для все случаи, за исключением странных ситуаций, таких как class A extends B здесь A и B были загружены разными загрузчиками класса. Это также может неправильно работать с другими языками JVM( например, Scala), где методы могут иметь одинаковое стирание, но не переопределять друг друга (например,void m(List<A> l) и void m(List<B> l), что вызовет ошибку компиляции в Java).

делать это правильно оказывается немного сложно, потому что вы должны убедиться, что два метода фактически переопределяют друг друга, и на данный момент нет метода, который делает это в Java SE. В любом случае, это полезный код для сайта.

package mcve.reflect;

import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;

public final class MCVEReflect {
    private MCVEReflect() {}

    /**
     * Returns the 0th element of the list returned by
     * {@code getAnnotations}, or {@code null} if the
     * list would be empty.
     * 
     * @param  <A> the type of the annotation to find.
     * @param  m   the method to begin the search from.
     * @param  t   the type of the annotation to find.
     * @return the first annotation found of the specified type which
     *         is present on {@code m}, or present on any methods which
     *         {@code m} overrides.
     * @throws NullPointerException if any argument is {@code null}.
     * @see    MCVEReflect#getAnnotations(Method, Class)
     */
    public static <A extends Annotation> A getAnnotation(Method m, Class<A> t) {
        List<A> list = getAnnotations(m, t);
        return list.isEmpty() ? null : list.get(0);
    }

    /**
     * Let {@code D} be the class or interface which declares the method
     * {@code m}.
     * <p>
     * Returns a list of all of the annotations of the specified type
     * which are either present on {@code m}, or present on any methods
     * declared by a supertype of {@code D} which {@code m} overrides.
     * <p>
     * Annotations are listed in order of nearest proximity to {@code D},
     * that is, assuming {@code D extends E} and {@code E extends F}, then
     * the returned list would contain annotations in the order of
     * {@code [D, E, F]}. A bit more formally, if {@code Sn} is the nth
     * superclass of {@code D} (where {@code n} is an integer starting at 0),
     * then the index of the annotation present on {@code Sn.m} is {@code n+1},
     * assuming annotations are present on {@code m} for every class.
     * <p>
     * Annotations from methods declared by the superinterfaces of {@code D}
     * appear <em>last</em> in the list, in order of their declaration,
     * recursively. For example, if {@code class D implements X, Y} and
     * {@code interface X extends Z}, then annotations will appear in the
     * list in the order of {@code [D, X, Z, Y]}.
     * 
     * @param  <A> the type of the annotation to find.
     * @param  m   the method to begin the search from.
     * @param  t   the type of the annotation to find.
     * @return a list of all of the annotations of the specified type
     *         which are either present on {@code m}, or present on any
     *         methods which {@code m} overrides.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static <A extends Annotation> List<A> getAnnotations(Method m, Class<A> t) {
        List<A> list = new ArrayList<>();
        Collections.addAll(list, m.getAnnotationsByType(t));
        Class<?> decl = m.getDeclaringClass();

        for (Class<?> supr = decl; (supr = supr.getSuperclass()) != null;) {
            addAnnotations(list, m, t, supr);
        }
        for (Class<?> face : getAllInterfaces(decl)) {
            addAnnotations(list, m, t, face);
        }

        return list;
    }

    private static Set<Class<?>> getAllInterfaces(Class<?> c) {
        Set<Class<?>> set = new LinkedHashSet<>();
        do {
            addAllInterfaces(set, c);
        } while ((c = c.getSuperclass()) != null);
        return set;
    }
    private static void addAllInterfaces(Set<Class<?>> set, Class<?> c) {
        for (Class<?> i : c.getInterfaces()) {
            if (set.add(i)) {
                addAllInterfaces(set, i);
            }
        }
    }
    private static <A extends Annotation> void addAnnotations
            (List<A> list, Method m, Class<A> t, Class<?> decl) {
        try {
            Method n = decl.getDeclaredMethod(m.getName(), m.getParameterTypes());
            if (overrides(m, n)) {
                Collections.addAll(list, n.getAnnotationsByType(t));
            }
        } catch (NoSuchMethodException x) {
        }
    }

    /**
     * @param  a the method which may override {@code b}.
     * @param  b the method which may be overridden by {@code a}.
     * @return {@code true} if {@code a} probably overrides {@code b}
     *         and {@code false} otherwise.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static boolean overrides(Method a, Method b) {
        if (!a.getName().equals(b.getName()))
            return false;
        Class<?> classA = a.getDeclaringClass();
        Class<?> classB = b.getDeclaringClass();
        if (classA.equals(classB))
            return false;
        if (!classB.isAssignableFrom(classA))
            return false;
        int modsA = a.getModifiers();
        int modsB = b.getModifiers();
        if (Modifier.isPrivate(modsA) || Modifier.isPrivate(modsB))
            return false;
        if (Modifier.isStatic(modsA) || Modifier.isStatic(modsB))
            return false;
        if (Modifier.isFinal(modsB))
            return false;
        if (compareAccess(modsA, modsB) < 0)
            return false;
        if ((isPackageAccess(modsA) || isPackageAccess(modsB))
            && !Objects.equals(classA.getPackage(), classB.getPackage()))
            return false;
        if (!b.getReturnType().isAssignableFrom(a.getReturnType()))
            return false;
        Class<?>[] paramsA = a.getParameterTypes();
        Class<?>[] paramsB = b.getParameterTypes();
        if (paramsA.length != paramsB.length)
            return false;
        for (int i = 0; i < paramsA.length; ++i)
            if (!paramsA[i].equals(paramsB[i]))
                return false;
        return true;
    }

    public static boolean isPackageAccess(int mods) {
        return (mods & ACCESS_MODIFIERS) == 0;
    }

    private static final int ACCESS_MODIFIERS =
        Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
    private static final List<Integer> ACCESS_ORDER =
        Arrays.asList(Modifier.PRIVATE,
                      0,
                      Modifier.PROTECTED,
                      Modifier.PUBLIC);
    public static int compareAccess(int lhs, int rhs) {
        return Integer.compare(ACCESS_ORDER.indexOf(lhs & ACCESS_MODIFIERS),
                               ACCESS_ORDER.indexOf(rhs & ACCESS_MODIFIERS));
    }
}

вот тест JUnit 4, а также:

package mcve.reflect;

import static mcve.reflect.MCVEReflect.*;

import java.lang.reflect.*;
import java.lang.annotation.*;
import java.util.*;
import static java.util.stream.Collectors.*;
import org.junit.*;
import static org.junit.Assert.*;

public class MCVEReflectTest {
    public MCVEReflectTest() {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface AnnoContainer {
        Anno[] value();
    }

    @Repeatable(AnnoContainer.class)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Anno {
        String value();
    }

    interface I {
        @Anno("I.m")
        void m();
    }
    interface J extends I {
        @Anno("J.m")
        @Override
        void m();
    }
    interface K {
        @Anno("K.m")
        void m();
    }

    @Test
    public void getAnnotationsTest() throws NoSuchMethodException {
        try {
            getAnnotations(null, Anno.class);
            fail();
        } catch (NullPointerException x) {
        }
        try {
            getAnnotations(List.class.getMethod("size"), null);
            fail();
        } catch (NullPointerException x) {
        }

        class A {
            @Anno("A.m")
            void m() {}
            @Anno("A.m(int)")
            void m(int a) {}
        }
        class B extends A implements K {
            @Anno("B.m 1")
            @Anno("B.m 2")
            @Override
            public void m() {}
        }
        class C extends B implements J {
            @Anno("C.m")
            @Override
            public void m() {}
        }

        System.out.println(Arrays.toString(B.class.getInterfaces()));

        List<Anno>   annos;
        List<String> expect;
        List<String> actual;

        annos  = getAnnotations(A.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("A.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(B.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("B.m 1", "B.m 2", "A.m", "K.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(C.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("C.m", "B.m 1", "B.m 2", "A.m", "J.m", "I.m", "K.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(J.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("J.m", "I.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos = getAnnotations(Object.class.getMethod("toString"), Anno.class);
        assertEquals(Collections.emptyList(), annos);
    }

    private boolean overrides(Method a, Method b) {
        return MCVEReflect.overrides(a, b);
    }

    private boolean overrides(Class<?> classA, String nameA,
                              Class<?> classB, String nameB)
            throws NoSuchMethodException {
        return MCVEReflect.overrides(classA.getDeclaredMethod(nameA),
                                     classB.getDeclaredMethod(nameB));
    }

    private boolean overrides(Class<?> classA, Class<?> classB, String name)
            throws NoSuchMethodException {
        return overrides(classA, name, classB, name);
    }

    @Test
    public void overridesTest() throws NoSuchMethodException {
        try {
            overrides(null, List.class.getMethod("size"));
            fail();
        } catch (NullPointerException x) {
        }
        try {
            overrides(List.class.getMethod("size"), null);
            fail();
        } catch (NullPointerException x) {
        }

        assertTrue("this is an override", overrides(ArrayList.class, "size",  List.class, "size"));
        assertFalse("same method",        overrides(List.class,      "size",  List.class, "size"));
        assertFalse("different methods",  overrides(ArrayList.class, "clear", List.class, "size"));

        class A { private void m() {} }
        class B extends A { private void m() {} }

        assertFalse("private methods", overrides(B.class, "m", A.class, "m"));

        class C { public void m() {} }
        class D { public void m() {} }

        assertFalse("no inheritance", overrides(D.class, "m", C.class, "m"));

        class E { public void m() {} }
        class F { public void m() {} }
        class G extends F { @Override public void m() {} }

        assertTrue("yes inheritance", overrides(G.class, "m", F.class, "m"));
        assertFalse("no inheritance", overrides(G.class, "m", E.class, "m"));

        class H {
            public void m(char a) {}
            public void m(long a) {}
        }
        class I extends H {
            @Override public void m(char a) {}
            @Override public void m(long a) {}
        }

        assertTrue("same parameters",
                   overrides(I.class.getDeclaredMethod("m", char.class),
                             H.class.getDeclaredMethod("m", char.class)));
        assertFalse("different parameters",
                    overrides(I.class.getDeclaredMethod("m", char.class),
                              H.class.getDeclaredMethod("m", long.class)));

        class SubHashMap extends HashMap<Object, Object> {
            void reinitialize() {
            }
        }

        String reinitialize = "reinitialize";
        // if this throws, find another method as a replacement
        Method m = HashMap.class.getDeclaredMethod(reinitialize);
        assertTrue(isPackageAccess(m.getModifiers()));
        assertFalse(Modifier.isFinal(m.getModifiers()));

        assertFalse("would override, except they are in different packages",
                    overrides(SubHashMap.class, HashMap.class, reinitialize));
    }

    @Test
    public void compareAccessTest() {
        assertEquals( 0, compareAccess(Modifier.PUBLIC,    Modifier.PUBLIC));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    Modifier.PROTECTED));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    0));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    Modifier.PRIVATE));
        assertEquals(-1, compareAccess(Modifier.PROTECTED, Modifier.PUBLIC));
        assertEquals( 0, compareAccess(Modifier.PROTECTED, Modifier.PROTECTED));
        assertEquals(+1, compareAccess(Modifier.PROTECTED, 0));
        assertEquals(+1, compareAccess(Modifier.PROTECTED, Modifier.PRIVATE));
        assertEquals(-1, compareAccess(0,                  Modifier.PUBLIC));
        assertEquals(-1, compareAccess(0,                  Modifier.PROTECTED));
        assertEquals( 0, compareAccess(0,                  0));
        assertEquals(+1, compareAccess(0,                  Modifier.PRIVATE));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   Modifier.PUBLIC));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   Modifier.PROTECTED));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   0));
        assertEquals( 0, compareAccess(Modifier.PRIVATE,   Modifier.PRIVATE));
        int notModsL = Modifier.PRIVATE | Modifier.PUBLIC;
        int notModsR = Modifier.PRIVATE | Modifier.PROTECTED;
        assertEquals("this might as well be undefined, but it's here for posterity",
                     0, compareAccess(notModsL, notModsR));
    }
}

Я думаю, у вас есть метод класса ужина и тест для аннотации. кроме этого java не поддерживает получение аннотаций super method.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Inherited
public @interface SomeAnnotation {
}

@SomeAnnotation
public class Parent {

    @SomeAnnotation
    public void someMethod() {
        System.out.println("Parent");
    }
}

public class Child extends Parent {

    @Override
    public void someMethod() {
        super.someMethod();
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = Child.class;

        System.out.println("Child Method");
        Method method = clazz.getMethod("someMethod");
        Arrays.stream(method.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));

        System.out.println("Parent Method");
        Method superMethod = clazz.getSuperclass().getMethod("someMethod");
        Arrays.stream(superMethod.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));

        System.out.println("Class Level support inherited annotations. ");
        Arrays.stream(clazz.getAnnotations()).forEach(annotation -> System.out.println(annotation));
    }
}

Также вы можете сделать какой-то общий способ доступа к классу ужин динамически. As

public void printAnnotationInMethod(String methodName, Class clazz) {
        try {
            Method method = clazz.getDeclaredMethod(methodName);
            Arrays.stream(method.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));
            if (null != clazz.getSuperclass()) {
                printAnnotationInMethod(methodName, clazz.getSuperclass());
            }
        } catch (NoSuchMethodException e) {
            //e.printStackTrace();
        } catch (SecurityException e) {
            //e.printStackTrace();
        }
    }

@Inhertied в аннотации поддержка только для аннотаций, которые используют тип. (только уровень классов)

Для справки аннотация наследования Java


нужно объявить @SomeAnnotation as @Inherited:

@Inherited
@interface SomeAnnotation {}

это то, что вы хотите, или я что-то пропустила?

    @Retention(RetentionPolicy.RUNTIME)
    @interface SomeAnnotation{
    }

    class Parent {
        @SomeAnnotation()
        public void someMethod(){

        }
    }

    class Child extends Parent {
        @Override
        public void someMethod(){
        }
    }

    class Test{
        public static void main(String[] args) throws NoSuchMethodException {
            Child child = new Child();
            Method someMethodChild = child.getClass().getMethod("someMethod");
            Class<?> superclass = someMethodChild.getDeclaringClass().getSuperclass();
            System.out.println(superclass.getMethod("someMethod").getAnnotations().length); //prints 1
        }
    }