Java Tutorial: Annotation and Reflection

Annotation and Reflection

  • Annotations have a number of uses, among them:
    • Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
    •   Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
    •   Runtime processing — Some annotations are available to be examined at runtime.
  • Annotation type declarations are similar to normal interface declarations.
  • An at-sign (@) precedes the interface keyword – Sample : public @interface ClassInfo
  • The annotations are not method calls and will not, by themselves, do anything.  Rather any tool like JPA need to extract the annotations at runtime and need to do execute the designed action like: Generating an object-relational mapping.
  • Following JAVA language constructs can be annotated: Class, Constructor, Field, Method, and Package
  • The Java compiler conditionally stores annotation metadata in the class files if the annotation has a RetentionPolicy of CLASS or RUNTIME. Later, the JVM or other programs can look for the metadata to determine how to interact with the program elements or change their behavior [ via Reflection API ]
  • Annotation can  also  be used to provide some info about who change a component [ Java Class , Java Methode ]

Custom Annotations Details for Runtime processing with Reflection

ClassInfo - @Target(value = ElementType.TYPE) 
  - Provide some Info who has changed a certain specific JAVA source
  - Use reflection code below to display the Anotation Info
     if(annotation instanceof ClassInfo)
     {
         ClassInfo myAnnotation = (ClassInfo) annotation;
         System.out.println(" -> autor           : " + myAnnotation.author());
         System.out.println(" -> date            : " + myAnnotation.date());
         System.out.println(" -> comment         : " + myAnnotation.comments());
     }

CanRun - @Target(value = ElementType.METHOD) 
   - Indicate that we can/should run a certain JAVA methode via reflection
   - Run that specific methode by using reflection code:  
         method.invoke(runner);

CanChange - @Target(value = ElementType.FIELD)  
   - Indicate that we can/should modify a certain JAVA methode via reflection 
   - Change a int field  by using reflection code:  
        f.setInt(runner,k);
 
CanConstruct - @Target(value = ElementType.CONSTRUCTOR) 
   - Indicates that we can/should run a certain JAVA Constructor via reflection
   - Construct a new AnnotationRunner instance and printout the int field id1  by using reflection code: 
       ctor.setAccessible(true);
       AnnotationRunner  r = (AnnotationRunner)ctor.newInstance();
       Field f = r.getClass().getDeclaredField("id1");
       f.setAccessible(true); 

Note all of the above Anotations are used during Runtime and thus  
@Retention(value = RetentionPolicy.RUNTIME) is mandatory

Java source: ClassInfo.java

package utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ClassInfo 
{
    String author() default "Helmut";
    String date();
    String comments();
}

Java source: CanChange.java

package utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CanChange 
    {
    }

Java source: CanRun.java

package utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CanRun {
}

Java source: CanConstruct.java

package utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.CONSTRUCTOR)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CanConstruct
    {
    }

Java source – The helper class: ReflectionUtils.java

package utils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * Die Klasse <code>ReflectionUtils</code> ist eine Utility-Klasse, die
 * verschiedene Hilfsmethoden zur vereinfachten Handhabung von Reflection
 * bereitstellt.
 * 
* @author Michael Inden
 * 
* Copyright 2011 by Michael Inden
 */
public final class ReflectionUtils
    {

    public static String modifierToString(final int modifier)
      {
        String modifiers = "";
        if (Modifier.isPublic(modifier))
          {
            modifiers += "public ";
          }
        if (Modifier.isProtected(modifier))
          {
            modifiers += "protected ";
          }
        if (Modifier.isPrivate(modifier))
          {
            modifiers += "private ";
          }
        if (Modifier.isStatic(modifier))
          {
            modifiers += "static ";
          }
        if (Modifier.isAbstract(modifier))
          {
            modifiers += "abstract ";
          }
        if (Modifier.isFinal(modifier))
          {
            modifiers += "final ";
          }
        if (Modifier.isVolatile(modifier))
          {
            modifiers += "volatile ";
          }
        if (Modifier.isSynchronized(modifier))
          {
            modifiers += "synchronized ";
          }
        return modifiers;
      }

    public static Field findField(final Class<?> clazz, final String fieldName)
      {
// Abbruch der Rekursion
        if (clazz == null)
          {
            return null;
          }
        try
          {
            return clazz.getDeclaredField(fieldName);
          } catch (final NoSuchFieldException ex)
          {
// rekursive Suche in Superklasse
            return findField(clazz.getSuperclass(), fieldName);
          }
      }

    public static Method findMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes)
      {
// Abbruch der Rekursion
        if (clazz == null)
          {
            return null;
          }
        try
          {
            return clazz.getDeclaredMethod(methodName, parameterTypes);
          } catch (final NoSuchMethodException ex)
          {
// rekursive Suche in Superklasse
            return findMethod(clazz.getSuperclass(), methodName, parameterTypes);
          }
      }

    
   public static Method[] getAllMethods(final Class<?> clazz)
      {
        final List<Method> methods = new ArrayList<Method>();
        methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
        /*
        if (clazz.getSuperclass() != null)
          {
              // rekursive Suche in Superklasse
            methods.addAll(Arrays.asList(getAllMethods(clazz.getSuperclass())));
          }
                */
        return methods.toArray(new Method[0]);
      }
       
    public static Field[] getAllFields(final Class<?> clazz)
      {
        final List<Field> fields = new ArrayList<Field>();
        //   Field[] fields = cls.getDeclaredFields();
        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
       
        return fields.toArray(new Field[0]);
      }
    
     public static Constructor[] getAllConstructors(final Class<?> clazz)
      {
        final List<Constructor> constructors = new ArrayList<Constructor>();
        //   Field[] fields = cls.getDeclaredFields();
        constructors.addAll(Arrays.asList(clazz.getConstructors()));
        return constructors.toArray(new Constructor[0]);
      }
    
    public static void printCtorInfos(final Constructor<?> ctor)
      {
        System.out.println(modifierToString(ctor.getModifiers()) + " " + ctor.getName()
                + buildParameterTypeString(ctor.getParameterTypes()));
        printAnnotations(ctor.getAnnotations());
      }

    public static void printMethodInfos(final Method method)
      {
        System.out.println(modifierToString(method.getModifiers()) + method.getReturnType() + " " + method.getName()
                + buildParameterTypeString(method.getParameterTypes()));
        printAnnotations(method.getAnnotations());
      }

    public static void printFieldInfos(final Field field)
      {
        System.out.println(ReflectionUtils.modifierToString(field.getModifiers()) + field.getType() + " "
                + field.getName());
        printAnnotations(field.getAnnotations());
      }

    public static String buildParameterTypeString(final Class<?>[] parameterTypes)
      {
        if (parameterTypes.length > 0)
          {
            return "(" + Arrays.toString(parameterTypes) + ")";
          }
        return "()";
      }

    private static void printAnnotations(final Annotation[] annotations)
      {
        if (annotations.length > 0)
          {
            System.out.println("Annotations: " + Arrays.toString(annotations));
          }
      }
    
    public static void printClassInfo(final Class<?> clazz )
      {
        // System.out.println("Canonical Class Name: " + clazz.getCanonicalName() );
        System.out.println("Class Name          : "  + clazz.getName() ); 
        System.out.println("Superclass Name     : "  + clazz.getSuperclass() ); 
        System.out.println("Interfaces          : "  + Arrays.toString(clazz.getInterfaces())); 
      //  Class c  = runner.getClass();
        Annotation[] annotations = clazz.getAnnotations();
         for(Annotation annotation : annotations)
           {
           if(annotation instanceof ClassInfo)
              {
              ClassInfo myAnnotation = (ClassInfo) annotation;
              System.out.println(" -> autor           : " + myAnnotation.author());
              System.out.println(" -> date            : " + myAnnotation.date());
              System.out.println(" -> comment         : " + myAnnotation.comments());
              }
           }
      }
            
    private ReflectionUtils()
      {
      }
    }

Java source: AnnotationRunner.java

package AnnotationTest;
import utils.CanChange;
import utils.CanRun;
import utils.CanConstruct;
import utils.ClassInfo;

@ClassInfo( author="Helmut Hutzer", 
            date="8-Feb-2014",
            comments="Intial Class Creation for Testing annotations" )
public class AnnotationRunner {
    @CanChange
    public int id1 = 1;
    public int id2= 2;
    
    @CanConstruct
    public AnnotationRunner()
        {
        }
    
    public AnnotationRunner(int v_id1, int v_id2)
    {
        id1 = v_id1;
        id2 = v_id2;
    }
    
    public void method1() 
    {
        System.out.println("Hello from method1 : " + id1);
    }

    @CanRun
    public void method2() 
    {
        System.out.println("Hello from method2 !");
    }
    
    public void set_id1(int v_id) 
    {
        id1 = v_id;
    }
    
    public void set_id2(int v_id) 
    {
        id2 = v_id;
    }
}

Java source: MyTest.java

package AnnotationTest;

import java.lang.annotation.Annotation;
import utils.CanChange;
import utils.CanRun;
import utils.CanConstruct;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class MyTest 
{
    public static void main(String[] args) 
    {
        AnnotationRunner runner = new AnnotationRunner();     
        
        System.out.println("\n-->Inspect Class:");
        utils.ReflectionUtils.printClassInfo( runner.getClass());
        
          System.out.println("\n-->Inspect Class:");
        utils.ReflectionUtils.printClassInfo(CanChange.class);
        
        System.out.println("\n--> Exploring Constructors  ");
        Constructor[] constructors =  utils.ReflectionUtils.getAllConstructors(runner.getClass());
        Constructor ctor =null;
        for (  Constructor  constructor : constructors) 
        {
            utils.ReflectionUtils.printCtorInfos(constructor);
                // Find the constructor without any paramter
            if (constructor.getGenericParameterTypes().length == 0)
                ctor = constructor;
        } 
        
          // See ; http://docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html 
        
        if ( ctor != null )
        {
            System.out.println("--> Found a constructor without parameters ");
            Annotation annos = ctor.getAnnotation(CanConstruct.class);
            if (annos != null) 
            {
                try
                {
                    System.out.println("--> Found Annotation CanConstruct   ");
                    utils.ReflectionUtils.printCtorInfos(ctor);
                    ctor.setAccessible(true);
                    AnnotationRunner  r = (AnnotationRunner)ctor.newInstance();
                    Field f = r.getClass().getDeclaredField("id1");
                    f.setAccessible(true);
                    System.out.println("--> Created new instance of class  AnnotationRunner");
                    System.out.println("--> print Field id1 via reflecion : "+   f.get(r));
                } catch ( InstantiationException | IllegalAccessException 
                            | NoSuchFieldException | InvocationTargetException ex ) 
                     { ex.printStackTrace(); }
            }
        }
        
        System.out.println("\n --> Exploring method annotations ");
         // utils.ReflectionUtils.getAllMethods scans your superclass too !
        Method[] methods =  utils.ReflectionUtils.getAllMethods(runner.getClass());
        
        for (Method method : methods) 
          {
            utils.ReflectionUtils.printMethodInfos(method);
            Annotation annos = method.getAnnotation(CanRun.class);
            if (annos != null) 
            {
                System.out.println("--> Found CanRun.class Annotation for method: " + method.getName() );
                try {
                    System.out.println("--> Invoking this method via reflection ");
                    method.invoke(runner);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            else
              {
              System.out.println("--> Found NO Annotation for method: " + method.getName() );  
              }
          }
//        Field[] fields   = runner.getClass().getFields();
        
        System.out.println("\n --> Exploring Attributes:");
        Field[] fields =  utils.ReflectionUtils.getAllFields(runner.getClass());
        for (Field f : fields) 
        {
            utils.ReflectionUtils.printFieldInfos(f);
            Annotation annos = f.getAnnotation(CanChange.class);
            if (annos != null) 
            {
                System.out.println("--> Found Annotation for field: " + f.getName() + " " + f.getType()  );  
                try 
                {                  
                    if (  f.getType().equals(int.class) )
                    {
                        int k = 99 ;
                        System.out.println("--> Current value for id1 : " + runner.id1 );
                        f.setInt(runner,k);
                        System.out.println("--> Changing int value for attr " +f.getName() + 
                                 " via reflection - New value: " + runner.id1 );
                    }
                } 
                catch (IllegalAccessException ex) 
                {
                    ex.printStackTrace();
                }
            }
        }
    }
} 

Program Output running MyTest.java

-->Inspect Class:
Canonical Class Name: AnnotationTest.AnnotationRunner
Class Name          : AnnotationTest.AnnotationRunner
Superclass Name     : class java.lang.Object
Interfaces          : []
 -> autor           : Helmut Hutzer
 -> date            : 8-Feb-2014
 -> comment         : Intial Class Creation for Testing annotations

-->Inspect Class:
Canonical Class Name: utils.CanChange
Class Name          : utils.CanChange
Superclass Name     : null
Interfaces          : [interface java.lang.annotation.Annotation]

--> Exploring Constructors  
public  AnnotationTest.AnnotationRunner([int, int])
public  AnnotationTest.AnnotationRunner()
Annotations: [@utils.CanConstruct()]
--> Found a constructor without parameters 
--> Found Annotation CanConstruct   
public  AnnotationTest.AnnotationRunner()
Annotations: [@utils.CanConstruct()]
--> Created new instance of class  AnnotationRunner
--> print Field id1 via reflecion : 1

--> Exploring method annotations 
public void method1()
--> Found NO Annotation for method: method1
public void method2()
Annotations: [@utils.CanRun()]
--> Found CanRun.class Annotation for method: method2
--> Invoking this method via reflection 
Hello from method2 !
public void set_id1([int])
--> Found NO Annotation for method: set_id1
public void set_id2([int])
--> Found NO Annotation for method: set_id2

 --> Exploring Attributes:
public int id1
Annotations: [@utils.CanChange()]
--> Found Annotation for field: id1 int
--> Current value for id1 : 1
--> Changing int value for attr id1 via reflection - New value: 99
public int id2

Reference