Indeterminate Heuristics Implementing the Null Object Pattern using AOP by Dale Asberry April 12, 2004
The Handy-Dandy Null Object Pattern Looked at from the AOP Perspective
In January 2003, I started writing a newsletter article titled "Aspect Oriented Programming: Using Aspects to Implement Design Patterns". When I found that many patterns had already been implemented and written up, I decided to scrap the article. However, I didn't find a Null Object pattern (NOPat) and decided that implementing it would be good practice.
While writing an earlier article Aspect Oriented Programming (AOP): Using AspectJ to implement and enforce coding standards, I was thinking about an anti-pattern that was annoying me at the time: null method return values and parameters. I was feeling some pain tracking down NPE's in untested code. I felt the NOPat could be leveraged to help me to at least find where the null assignments were being made. Since I didn't own, or in some cases, have access to the source, I felt that AOP could help me implement the NOPat.
The first thing I realized (partly through trial-and-error) was that I needed pointcuts that matched both null assignments to class member variables and null return value assignments to any variables. Both types of pointcuts are needed because matching on assignments only works for class member variables and not method variables. On the other hand, matching on calls to methods with return values misses direct variable assignments such as "a = b" or "a = null".
Let's take a look at the pointcut to match assignments:
matches all assignment statements in the code that is to be aspected. The args joinpoint matches, and binds, the value of the object being assigned. The !within joinpoint is following a typical AOP idiom -- all aspected(1) code but not the aspect (and related instrumentation).
Next, we'll take a look at the pointcut to match assignments occurring due to method calls:
The call joinpoint matches all method calls that return a value(2). The target joinpoint matches and binds the object of the method being invoked.
Now, let's look at the "handler" code:
As I was thinking about how to implement the handler, I realized that I needed some way to differentiate Null Object instances(3). Each instance reflects the stack trace of where the null assignment was originally made. HashMap immediately popped in my head -- using a HashMap is another useful idiom for maintaining aspect state between advice executions. Since each Null Object instance would have a unique hashcode, I realized, "problem solved!"
Object around(Object pCalledObject): callsThatReturnAnObject(pCalledObject)
//Is the program trying to invoke a method call on a Null Object instance?
NullPointerException lNpe = new NullPointerException();
lRetVal = proceed(pCalledObject);
//Did the method return null?
if(lRetVal == null)
NullPointerException lE = new NullPointerException();
lRetVal = lErrorMethod.getReturnType().newInstance();
Object around(Object pSetValue): nullMemberAssignments(pSetValue)
if(pSetValue == null)
NullPointerException lE = new NullPointerException();
lRetVal = lErrorField.getFieldType().newInstance();
The first problem I ran into was an unexpected and unhandled NPE happening in the aspect's advice. Creating a new object using Class.newInstance() was returning a null. But why?... because the "class" was an interface! Ouch - no apparent solution. I put the project away for a couple days and was happily distracted by fatherhood. When I came back, I realized almost immediately that Java 1.3's dynamic proxy fits the problem perfectly.
I didn't really have any problems at this stage, however, I didn't like the aspectj instrumented code showing up in the stack traces since it could be confusing to a non-aspect programmer or end-user. After a little code refactoring, everything was in place(4)!
Here's the sample output from test.Main:
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullStringTest(Main.java:32)
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullArrayListTest(Main.java:42)
Caused by: java.lang.NullPointerException: Null value assigned to field test.Main.cList(Main.java:22)
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullArrayListTest(Main.java:62)
Exception in thread "main"
I have a couple of theories about some potential bugs that may need to be fixed. Write test and code to verify that:
... Null Object references are not being GC'd if the variable originally containing it is assigned a real object.
... if a variable has multiple interfaces in it's class definition and if it is assigned a Null Proxy and is dereferenced as one of the other interfaces then it will throw a ClassCastException.
1. compiled by the aspectj compiler ajc. 2. those methods that return "void" have a different signature and is irrelevant since the compiler won't allow assigning void to an object. 3. I worked the problem backwards... I knew what the solution would sort of look like, but the direction of where to start was too fuzzy. 4. although I didn't mention it directly, I maintained a simple testing class rather than full-on JUnit for simple TDD.
R. Dale Asberry been hacking since 1978, professionally since 1990. He's certified in Java 1.1 and has a four digit MCP number. He discovered Jini at the 2000 JavaOne and has been building incredibly cool, dynamic, distributed architectures ever since! Over time, he's discovered several principles that have contributed to his success - they are the Princples of: Enabling Others, Simplicity, No Complaining, Least Work, Least Surprise, Least Damage, and "It Just Works".