Google

Apr 17, 2014

Understanding Java custom annotation with a practical example

In real life Java applications, you need to provide service retries on failures. For example, retry 3 times at the interval of 3 seconds, etc. Here is a custom Java annotation example.

Step 1: Define the Retry run-time annotation to be applied to the declared fields.


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

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

 int attempts() default 3;
 int delayInSeconds() default 1; // default 1 second

}


Step 2:  Use the above custom run-time annotation in the PrinterServiceUtil, so that it will retry the service (e.g. DummyService) that it wraps.

public class PrintServiceTest {

 @Retry(attempts = 3, delayInSeconds = 3) //retry 3 times @ 3 seconds interval
 private PrintServiceUtil printService;

 public static void main(String[] args) {

  PrintServiceUtil printService = new PrintServiceUtil();
  printService.print("Hello");
 }
}


Step 3PrinterServiceUtil  class that uses the annotation to decide, if retry is required, and if required, what retry count and retry values to use by extracting them from the annotation.

import java.lang.reflect.Field;

/**
 * A bit convoluted method to demonstrate custom annotation to keep it simple without
 * AOP and Dependency injection frameworks, which are more suited.
 * 
 */

public class PrintServiceUtil {

 private DummyService service;

 public void print(String message) {

  //if PrintServiceUtil invoking this is annotated with retry
  //use dynamic proxy design pattern to retry if the service is down
  Field[] fields = getCallerClassName().getDeclaredFields();
  boolean serviceCreated = false;

  for (Field field : fields) {
   Retry annotation = field.getAnnotation(Retry.class);
   if (annotation != null && field.getType() == PrintServiceUtil.class) {
    service = (DummyService) RetryProxy.newInstance(new DummyServiceImpl(), annotation.attempts(),
      annotation.delayInSeconds());
    serviceCreated = true;
   }
  }

  //if not annotated, retry is not required
  if (!serviceCreated) {
   service = new DummyServiceImpl();
  }

  service.execute(message); //execute the service
 }

 
 
 /**
  * gets the calling class from the stack trace
  * @return
  */
 private Class getCallerClassName() {
  String callerClassName = null;

  try {
   callerClassName = new Exception().getStackTrace()[2].getClassName();
   return Class.forName(callerClassName);
  } catch (Exception ex) {}

  throw new RuntimeException("Error getting caller class");
 }
}


Step 4: The DummyService interface and implementations are similar to the dynamic proxy tutorial -- retry example.

public interface DummyService {
     abstract void execute(String message);
}


public class DummyServiceImpl implements DummyService {
 
 private int count = 0 ;
 
 public void execute(String message) {
  count++;
  if(count % 3 == 0){
   System.out.println (message);
  }
  else {
   throw new RuntimeException("Service Cannot be accessed ..... ");
  }
 }

}



Step 5: The dynamic proxy class that performs the actual retry.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

public class RetryProxy<T> implements InvocationHandler {

 final T delegate; // underlying object
 int retryCount;
 long delay;

 //create a proxy
 public static Object newInstance(Object obj, int retryCount, long delay) {
  return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
    obj.getClass().getInterfaces(), new RetryProxy(obj, retryCount, delay));
 }

 private RetryProxy(T underlying, int retryCount, long delay) {
  this.delegate = underlying;
  this.retryCount = retryCount;
  this.delay = delay;
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  int retries = 0;
  boolean completed = false;
  Object ret = null;

  while (!completed) {
   try {
    ret = method.invoke(delegate, args);
    completed = true;
   } catch (Exception e) {
    retries++;
    if (retries > retryCount) {
     completed = true;
     throw e;
    }
    
    TimeUnit.SECONDS.sleep(delay);
    System.out.println("Retrying the service. Retry count " + retries);
   }
  }

  return ret;

 }
}


Output:

Retrying the service. Retry count 1
Retrying the service. Retry count 2
Hello

Change the annotation properties and test it.

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home