Scanning Method and Field Level Annotations

0

In this article we will see how to scan the current class and the super classes for annotations defined at method and field level. Since repetitive scanning can be an expensive process will show how to cache the annotation type and the corresponding members (methods or fields).

Method Level Annotations

Lets first define couple of method level annotations.

MethodAnnot1:

package com.javarticles.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodAnnot1 {
}

MethodAnnot2:

package com.javarticles.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodAnnot2 {
}

Field Level Annotations

We will also define couple of field level annotations.

FieldAnnot1:

package com.javarticles.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface FieldAnnot1 {
}

FieldAnnot2:

package com.javarticles.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface FieldAnnot2 {
}

Define classes using annotation

We will define three classes in a hierarchy. Class A extends B and B in turn extends Class C. Each class contain methods and fields annotated, our task would be to scan these annotations and cache the annotation type along with the method or field against which the annotation is used.

A:

package com.javarticles.annotations;

public class A extends B {
    @FieldAnnot1
    private String a1;
    
    @FieldAnnot1
    private String a2;
    
    @MethodAnnot1
    public void am1() {
        
    }
    
    @MethodAnnot2
    public void am2() {
        
    }
    
    @MethodAnnot1
    public void am3() {
        
    }
    
    @Override
    @MethodAnnot2
    public void bm2() {
        
    }
    
    @Override
    @MethodAnnot1
    public void cm1(String name) {
        
    } 
}

B:

package com.javarticles.annotations;

public class B extends C {
    @FieldAnnot1
    private String b1;
    
    @FieldAnnot1
    private String b2;
    
    @FieldAnnot1
    private String c1;
    
    @MethodAnnot1
    public void bm1() {
        
    }
    
    @MethodAnnot1
    public void bm2() {
        
    }
}

C:

package com.javarticles.annotations;

public class C {
    @FieldAnnot1
    private String c1;
    
    @MethodAnnot1
    public void cm1() {
        
    }    
    
    @MethodAnnot1
    public void cm1(String name) {
        
    }  
}

Get Annotation List at Method and Field Level

The java reflection APIs that you need to use are:

  1. Field.getAnnotations() – to get the field annotations
  2. Method.getAnnotations() – to get method level annotations

How can we check whether a method or a field is overridden? In which case, we need to skip scanning if we have already scanned the sub class based member and found the same member in of its super class. In order to determine whether a member is overridden we need to now its name.
If the member is a method, we also need to know the parameter types and make sure all of them match.
Java reflection APIs used here are:

  1. Field.getName() – Name of the field
  2. Method.getName() – Name of the method
  3. Method.getParameterTypes() – Method parameters

We need to scan for the annotations and from both field and method object. We also need to know whether the field or method is overridden. Since both field and method member needs this, we have extracted the common behavior to AbstractMethodOrField which FieldMember and MethodMember extends.

FieldMember:

package com.javarticles.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;


public class FieldMember extends AbstractMethodOrField<FieldMember> {
    Field field;

    FieldMember(Field field) {
        this.field = field;
    }

    public Annotation[] getAnnotations() {
        return field.getAnnotations();
    }

    public String getName() {
        return field.getName();
    }

    public String toString() {
        return field.toString();
    }
    
    public boolean isOverridenBy(FieldMember fieldMember) {
        boolean overidden = super.isOverridenBy(fieldMember);
        if (overidden) {
            System.out.println(fieldMember + " overidden by \n" + field);
        }
        return overidden;
    }
}

MethodMember:

package com.javarticles.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;


public class MethodMember extends AbstractMethodOrField<MethodMember> {
    Method method;

    MethodMember(Method method) {
        this.method = method;
    }

    public Annotation[] getAnnotations() {
        return method.getAnnotations();
    }

    public String getName() {
        return method.getName();
    }

    public boolean isOverridenBy(MethodMember methodMember) {
        boolean nameDifferent = !super.isOverridenBy(methodMember);
        if (nameDifferent) {
            return false;
        }
        Method other = methodMember.method;
        if (other.getParameterTypes().length != method.getParameterTypes().length) {
            return false;
        }
        for (int i = 0; i < other.getParameterTypes().length; i++) {
            if (!other.getParameterTypes()[i].equals(method
                    .getParameterTypes()[i])) {
                return false;
            }
        }
        System.out.println(methodMember + " overidden by \n" + method);
        return true;
    }

    public String toString() {
        return method.toString();
    }
}

AbstractMethodOrField:

package com.javarticles.annotations;

import java.lang.annotation.Annotation;
import java.util.List;

public abstract class AbstractMethodOrField<T extends AbstractMethodOrField<T>> {

    public boolean isOverridenBy(List<T> members) {
        for (T each : members) {
            if (isOverridenBy(each)) {
                return true;
            }
        }
        return false;
    }

    public boolean isOverridenBy(T methodOrField) {
        return getName().equals(methodOrField.getName());
    }

    abstract Annotation[] getAnnotations();

    abstract String getName();
}

Example to scan Field/Method annotations

In the below example we will get the class hierarchy and loop through each class from the class to be scanned to its super class up in the hierarchy.
Given a class, we get the declared methods and the fields and scan for annotations. We the cache the retrieved annotations and finally print the annotation type and the methods/fields using it.

Java Reflection APIs used are:

  1. Class.getSuperclass() – Get the super class
  2. Class.getDeclaredMethods() – Get the declared methods
  3. Class.getDeclaredFields() – Get the declared fields

AnnotationScanningExample:

package com.javarticles.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AnnotationScanningExample {
    public static void main(String[] args) {
        for (Class<?> eachClass : getSuperClasses(A.class)) {            
            for (Method method : eachClass.getDeclaredMethods()) {
                addToAnnotationLists(new MethodMember(method),
                        methodsForAnnotations);
            }
            for (Field field : eachClass.getDeclaredFields()) {
                addToAnnotationLists(new FieldMember(field),
                        fieldsForAnnotations);
            }
        }
        System.out.println("__________________________________________________________");
        methodsForAnnotations.forEach((k, v) -> {
            System.out.println(k + "->");
            v.forEach(System.out::println);
        });
        System.out.println("__________________________________________________________");
        fieldsForAnnotations.forEach((k, v) -> {
            System.out.println(k + "->");
            v.forEach(System.out::println);
        });
    }

    private static <T extends AbstractMethodOrField<T>> void addToAnnotationLists(
            T methodOrField, Map<Class<?>, List<T>> memberAnnotationMap) {
        for (Annotation annotation : methodOrField.getAnnotations()) {
            Class<? extends Annotation> type = annotation.annotationType();
            List<T> methodOrFieldList = getAnnotatedMembers(
                    memberAnnotationMap, type);
            if (methodOrField.isOverridenBy(methodOrFieldList)) {
                return;
            }
            methodOrFieldList.add(methodOrField);
        }
    }

    private static List<Class<?>> getSuperClasses(Class<?> testClass) {
        ArrayList<Class<?>> results = new ArrayList<Class<?>>();
        Class<?> current = testClass;
        while (current != null) {
            results.add(current);
            current = current.getSuperclass();
        }
        return results;
    }

    private static <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map,
            Class<? extends Annotation> type) {
        if (!map.containsKey(type)) {
            map.put(type, new ArrayList<T>());
        }
        return map.get(type);
    }

    static final Map<Class<?>, List<MethodMember>> methodsForAnnotations = new HashMap<Class<?>, List<MethodMember>>();
    static final Map<Class<?>, List<FieldMember>> fieldsForAnnotations = new HashMap<Class<?>, List<FieldMember>>();
}

Output:

public void com.javarticles.annotations.A.cm1(java.lang.String) overidden by 
public void com.javarticles.annotations.C.cm1(java.lang.String)
private java.lang.String com.javarticles.annotations.B.c1 overidden by 
private java.lang.String com.javarticles.annotations.C.c1
__________________________________________________________
interface com.javarticles.annotations.MethodAnnot1->
public void com.javarticles.annotations.A.am1()
public void com.javarticles.annotations.A.am3()
public void com.javarticles.annotations.A.cm1(java.lang.String)
public void com.javarticles.annotations.B.bm2()
public void com.javarticles.annotations.B.bm1()
public void com.javarticles.annotations.C.cm1()
interface com.javarticles.annotations.MethodAnnot2->
public void com.javarticles.annotations.A.am2()
public void com.javarticles.annotations.A.bm2()
__________________________________________________________
interface com.javarticles.annotations.FieldAnnot1->
private java.lang.String com.javarticles.annotations.A.a1
private java.lang.String com.javarticles.annotations.A.a2
private java.lang.String com.javarticles.annotations.B.b1
private java.lang.String com.javarticles.annotations.B.b2
private java.lang.String com.javarticles.annotations.B.c1

Download the source code

This was an example about scanning annotations at method and field level.

You can download the source code here: javaAnnotationScanningExample.zip

About Author

Ram’s expertise lies in test driven development and re-factoring. He is passionate about open source technologies and loves blogging on various java and open-source technologies like spring.
You can reach him at [email protected]

Comments are closed.