JavaFX Tutorials

Monday, August 20, 2012

A Custom Java RUNTIME Annotation

Everyone coding in Java today uses annotations, from the @Entity of a Hibernate app to the @Test of JUnit.  But projects often still rely on Maps or Spring config files rather than use a well-placed custom annotation.
Annotations were added to Java in version 5.  There are a few types of annotations based on who is using the annotation: SOURCE, CLASS, and RUNTIME.  This example demonstrates a custom RUNTIME annotation which means that the annotation is available via Java Reflection.  (The other types, including the default 'CLASS', are not available by Java Reflection.)
Auditable

In this example, there is an EJB 'ContactBean' that has methods for adding, updating, and deleting a Contact.  Given a functional requirement to log the operation, the example will map the operations to one of three types of messages: NEW, UPDATE, DELETE.  There's one NEW and one DELETE operation, but multiple UPDATES (updateInfo, changeEmail, updaetImage) in ContactBean.


Here is the Java code for an enum 'AuditType':

  public enum AuditType {
     NEW, UPDATE, DELETE
  }


An annotation called Auditable is used to link an AuditType to the relevant ContactBean method.  Auditable, the annotation has a single operation 'value()'.  It could have been called ''auditType', but value() is an optimized syntax that allows the developer to leave off the attribute name (for example 'auditType').  The ElementType of the annotation is METHOD, indicating that it's for a method rather than a class.  The Retention is 'RUNTIME' meaning that it will be available in Reflection calls.

This is the source of an annotation called Auditable

  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.METHOD)
  public @interface Auditable {

    AuditType value();
  }


Finally, here is the marked-up ContactBean.  Note that all of the updates -- updateImage, updateInfo, changeEmail -- map to the same Auditable while create() and delete() have their own.  The code also references an @Interceptors annotation which is not required to work with @Auditable and will be explained later.

@Stateless
public class ContactBean {

   @Auditable(AuditType.NEW)
   @Interceptors({AuditInterceptor.class})
   public Long create(String email, String firstName, String lastName){
      // TODO: implement
      return 0L;
   }

   @Auditable(AuditType.UPDATE)
   @Interceptors({AuditInterceptor.class})
   public void updateImage(Long id, String url){
      // TODO: implement
   }

   @Auditable(AuditType.UPDATE)
   @Interceptors({AuditInterceptor.class})
   public void updateInfo(Long id, String firstName, String lastName){
      // TODO: implement
   }

   @Auditable(AuditType.UPDATE)
   @Interceptors({AuditInterceptor.class})
   public void changeEmail(Long id, String email){
      // TODO: implement
   }

   @Auditable(AuditType.DELETE)
   @Interceptors({AuditInterceptor.class})
   public void delete(Long id){
      // TODO: implement
   }

}//end ContactBean


Application

One possible implementation of an auditing function is to use EJB 3 Intereceptors.  Note that the custom annotation @Auditable doesn't require an EJB 3 Interceptor or anything else from Java EE.  The class diagram shows an arrangement of classes where auditing is handled by a class called 'AuditInterceptor'.


Custom Annotation and Audit EJB 3 Interceptor
Note that although AuditInterceptor will be recording ContactBean's operations, AuditInterceptor has no knowledge of the ContactBean class.  The use of the annotation separates the classes.

Here is the source code for AuditInterceptor which features some Reflection calls to pull the annotation (NEW, UPDATE, DELETE) from the associated ContactBean method.  ContactBean is an EJB and calling one of these methods will automatically call the AuditInterceptor.

public class AuditInterceptor {

   @AroundInvoke
   public Object recordOperation(InvocationContext ic) throws Exception {

      Method m = ic.getMethod();
      if( m.isAnnotationPresent(Auditable.class) ) {
        log( m.getAnnotation(Auditable.class).value().toString() );
      }

     return ic.proceed();
   }

  private void log(String type) {
     System.out.println("a " + type + " operation occurred" );
   }
}


Summarize

I've seen projects use Spring data structures or statically-defined Maps to make an association between a method and something else like a type.  I prefer the annotation because of the simpler syntax and because it locates the extra info with where it is used.  This helps maintenance developers since it's one fewer file to have to chase down when working on a ticket.





No comments:

Post a Comment