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.