In JEE6 there is a NotNull annotation which is of very limitted functionality. In my recent project I came across a requirement to check methods parameters for null. Writing if blocks in all the methods is not a good solution. So I decided to go with an AOP solution. I used AspectJ for all my AOP stuff in this blog. Here is what I did, I created an annotation called NotNull and an Aspect with Around pointcut to intercept method calls which has its parameters annotation with NotNull annotation.
Now I will walk through the code.
Code Snippet for NotNull Annotation:
package org.naveen.maven.research.annotations; 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.PARAMETER) public @interface NotNull { /** * Message set in the exception thrown when parameter is null. */ String message() default "Parameter value is not nullable."; /** * Will specify how null values have to be treated. */ NotNullPolicy policy() default NotNullPolicy.THROW_EXCEPTION; /** * If you want to throw a custom exception. * By default this will throw an IllegalArgument Exception. */ Class<? extends Throwable> exception() default IllegalArgumentException.class; }
Here is the NotNullPolicy enumeration:
package org.naveen.maven.research.annotations; public enum NotNullPolicy { /** Irrespective of method body, method will return null if all arguments with this policy is null. */ RETURN_NULL, /** Will throw an exception. [Default] */ THROW_EXCEPTION, /** Don't do anything and will continue with method body */ CONTINUE, /** Irrespective of method body, method will return null. */ RETURN_NULL_IMMEDIATE }
And here comes the backbone, the Aspect.
package org.naveen.maven.research.aspects; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.naveen.maven.research.annotations.NotNull; import org.naveen.maven.research.annotations.NotNullPolicy; @Aspect public class NotNullAspect { private static final Logger logger = Logger.getLogger(NotNullAspect.class.getName()); @Around("execution(* org.naveen..*(..,@NotNull (*),..)) && " + "(!within(org.naveen.maven.research.annotations.*))") public Object doNullParamCheck(ProceedingJoinPoint jp) throws Throwable { List<Boolean> lazyNullRtValue = new LinkedList<Boolean>(); boolean hasToContd = false; Object[] args = jp.getArgs(); Signature sig = jp.getSignature(); if(sig instanceof MethodSignature) { MethodSignature mtdSig = MethodSignature.class.cast(sig); Method mtd = mtdSig.getMethod(); logger.log(Level.INFO, String.format("Checking parameters for method --> %s", mtd.getName())); Annotation[][] anns = mtd.getParameterAnnotations(); for(int i=0;i<anns.length;++i) { if(anns[i].length > 0) { if(anns[i][0] instanceof NotNull) { NotNull ntnAnn = (NotNull) anns[i][0]; logger.log(Level.INFO, String.format("NotNull handling policy is set to %s", ntnAnn.policy())); if(args[i] == null) { switch(ntnAnn.policy()) { case THROW_EXCEPTION: Class<? extends Throwable> expCls = ntnAnn.exception(); logger.log(Level.INFO, String.format("Exception class set is %s", expCls.getName())); if(expCls == IllegalArgumentException.class) { throw new IllegalArgumentException( String.format("In Method %s --> %s", mtd.getName(), ntnAnn.message())); } else { Constructor<? extends Throwable> ct = expCls.getConstructor(String.class); Throwable t = ct.newInstance(String.format("In Method %s --> %s", mtd.getName(), ntnAnn.message())); throw t; } case RETURN_NULL_IMMEDIATE: return null; case RETURN_NULL: logger.log(Level.WARNING, "One NotNull annotated argument has null value. Continuing with lazy checking."); lazyNullRtValue.add(false); break; case CONTINUE: hasToContd = true; lazyNullRtValue.add(true); break; } } else { hasToContd = true; if(ntnAnn.policy() == NotNullPolicy.RETURN_NULL) lazyNullRtValue.add(true); } } } } } logger.log(Level.INFO, String.format("Argument length is %d and LazyReturn list is %s and hasToContd %s", args.length, lazyNullRtValue, hasToContd)); if(hasToContd && checkLazyNull(lazyNullRtValue)) return jp.proceed(); return null; } private boolean checkLazyNull(List<Boolean> list) { if(list.size() == 0) return true; for(Boolean b : list) if(b) return b; return false; } }
@Around("execution(* org.naveen..*(..,@NotNull (*),..)) && " + "(!within(org.naveen.maven.research.annotations.*))")
- Which has methods which is declared inside classes which are declared in org.naveen package or sub package of it.
- Which has methods whoose paramters are annotated with NotNull annotation.
- Dont weave classes inside org.naveen.maven.research.annotations package or sub package of it.
Different Usage examples:
Throws custom exception:
public String test(String message, @NotNull(exception=ParameterException.class) String name) { return message + name; }
Throws default exception with custom message:
private String privateTest(@NotNull String message, @NotNull(message="Name cannot be null.") String name) { return message + name; }
private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL_IMMEDIATE) String greet) { System.out.println("Message received is " + greet); return "Hello World " + greet; }
Suppose if you have a scenario like this:
- You have a method which will accept 2 arguments.
- Method returns null, if both arguments are null.
- Execute method, if one of the argument is not null.
private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL) String message, @NotNull(policy=NotNullPolicy.RETURN_NULL, message="Name cannot be null.") String name) { System.out.println("Message received is " + message); return "Hello World " + name; }
You can combine both NotNullPolicy.RETURN_NULL and NotNullPolicy.RETURN_NULL_IMMEDIATE together.
private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL) String message, @NotNull(policy=NotNullPolicy.RETURN_NULL_IMMEDIATE) String greet, @NotNull(policy=NotNullPolicy.RETURN_NULL, message="Name cannot be null.") String name) { System.out.println("Message received is " + message); return "Hello World " + name; }
In the above example, we are saying :
- Execute method body, if either of the arguments 'message' or 'name' is not null AND greet is not null.
- Return Null, if both arguments 'message' and 'name' is null
- Return Null, if argument 'greet' is Null
All Resources used in this project are available in My GitHub Repo.