Monday, 4 July 2011

Auditing using AOP and annotations in Spring

Lord, how I dislike the old XML configuration for AOP (among other things).

I have not given myself the opportunity to try out Spring's annotation based AOP in spring, until now and boy is it easy (with one minor gotcha).

Here are the building blocks:

1) Make your own Annotation. This is a simple way to mark all methods you want to audit.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ParmsAudit
{
  ActionAuditType  auditType();
}


2) ActionAuditType is an enum that will be used to identify the thing being audited.

public enum ActionAuditType {
USER_LOGGED_IN("User Logged In"),
USER_LOGGED_OUT("User Logged Out");

private String mDescription;
  private ActionAuditType(String aDescription) {
 mDescription = aDescription;
}


public String getDescription() {
 return mDescription;
}
public void setDescription(String aDescription) {
 mDescription = aDescription;
}
}

3) Create an Aspect. This is the code that is run when the method marked by the annotation is called.

Here is the minor gotcha - well for me atleast.
I had to list the full package path to the Annotation in the @Before statement.


@Aspect
public class AuditAspect {

@Before(value="execution(@uk.co.billcomer.audit.ParmsAudit * *(..)) &&   @annotation(parmsAudit)", argNames="parmsAudit")
public void logTheAuditActivity(JoinPoint aPoint, ParmsAudit parmsAudit) {
 String userName = getUserName();
 mlogger.info("Parms Auditing User Name: " + userName);
 mlogger.info("auditType:" + parmsAudit.auditType().getDescription());

 String arguments = getArgs(aPoint.getArgs());

 if (arguments.length() > 0) {
 mlogger.info("args-" + arguments);
 }
}

private String getArgs(Object[] args) {
 String arguments = "";
 int argCount = 0;

 for (Object object : args) {
   if (argCount > 0) {
     arguments += ", ";
   }
   arguments += "arg[" + ++argCount + "]=" + "[" + object + "]";
 }
 return arguments;
}

private String getUserName() {
 try {
   return SecurityContextHolder.getContext().getAuthentication().getName();
 } catch (NullPointerException npe) {
   return "Unknown User";
 }
}
}

4) Mark the method to be audited.


@Service("parms.DummyAuditThing")
public class DummyAuditThing implements DummyAudit {

@Override
@ParmsAudit(auditType = ActionAuditType.USER_LOGGED_IN)
public void aspectAuditMethodTwoParams(String param1, Long param2) {
 mlogger.info("In method to be audited param1[" + param1 + "], p2[" + param2 + "]");
}
}


5) Context configuration
This is needed to speed up the compilation process. Essentially it tells the compiler what classes will have Aspects
   
   <aop:aspectj-autoproxy proxy-target-class="true"/>   
   
   <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
   <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
   
   <bean id="bill.auditAspect" class="uk.co.billcomer.audit.AuditAspect" />