How many of you have written a caching solution for one of your expensive method calls? Or how about retry logic for your service calls?
Living in such a connected world no single application is its own island. For every applications I have worked for, there always external dependencies, either other service APIs or
databases that we need to rely upon. And we can’t make the assumption that they are reliable 100% either.
Some service calls are expensive and the underlying data don’t change so often. We tend to cache the results. A naive approach would be to find such methods and refactor them
so that cache can be used before the call to the services are made. The better way is to use AOP where you intercept those calls with ‘aspects’. The in those aspects you can make the decision to whether get the results from the cache or to make the expensive service calls.
Another common scenario is retry when you encounter exceptions in your service calls: transient network issues (latency, timed out, spillover in load balancer…) or database hiccups.
Normally, you should at least retry the calls for several times before giving up. Like noted previously, a naive approach would be to go every methods and apply the retry logic. Or you could use AOP.
In this post, I’m going to talk about how to use aspect oriented way to ease the refactoring effort. I will not talk about the full blown bytecode level AOP solution which uses AspectJ
with bytecode weaving. Instead, I will talk about a lighter weight of aspect programming using the Java’s dynamic proxy and its reflection mechanism. I think it’s pretty similar to the way Spring
AOP works. The only difference is that my code will assume every method calls implement interfaces. Thus, it will not have to use cglib to generate the proxies. Also, I think programming to interface
is a much cleaner and prefer way for your service calls Data access objects (DAO).
At the end you could decorate your method with annotations/aspects like this:
Example
1234567
@Timeit@Retry(times=3)@Cache(timeToLiveInSeconds=3600)publicvoidgoGetMyData(StringsomeParam,intanotherOne){// do something}
Let’s define an example interface for the DAO and its implementation.
I intentionally throw a RuntimeException 30% of the times this method run to simulation transient error that could be retried.
Now is the fun part: we will add additional functionalities over this method without modifying its code. As the begining I want to time the method performance
and retry if it fails (up to 3 times before I give up).
The easiest way to do this is to use annotation to denote your new aspects.
In order to facilitate the annotations in the java dynamic proxy, we need to create an InvocationHandler for each of those annotations.
For this I first borrow the utility class from “Java Reflection in Action”. You can get full source at the end of this post.
I then create a base Interceptor on top of this invocation handler to make the dynamic proxy generation handling annotations easier.
The nice thing in doing this is that in order to create an aspect based on an annotation you just need to implement the Invoker interface shown above.
Then you can create the dynamic proxy of the targeted object by calling:
Creating the Dynamic Proxy
123
// Some dao needed to be wrapped in Timeit aspectSomeDaodao=...dao=Interceptor.createProxy(dao,Timeit.class,newTimerAspect());
Interceptor.createProxy takes 3 arguments: the targeted object to be proxied, the aspect annotation class and the object to handle the aspect.
For the Timer (or Timeit) aspect, it could be as simple as this:
Here is why this is an aspect: the execute method takes note of the current time. It then invokes the original method call. Finally it calculates
how long this method call takes. I believe in AspectJ this is called “before and around pointcut”.
Similarly, I would create the Retry aspect by implementing the Invoker interface and call
After we have the aspects to handle those annotations, how do chain them in a correct order, an order which makes sense at all?
It all depends on your aspects’ logic but in this case I would make the Timer aspect outside of the Retry aspect. Confused? Here is the order of execution:
1. Enter the Timer aspect, take note of the current time
2. Enter the Retry aspect, retry count set to 0
3. Invoke the actual Dao method
4. If it fails, retry aspect catch the exception and retries! It keeps track of the number of retries (up to 3 times by default)
5. Either the call fails if retries exceed 3 times or it exits the Retry aspect and yield the command to Timer Aspect again
6. Timer aspect calculate how long this Dao method takes
7. Return the result to the caller
One thing you need to pay close attention is the order of the execution of those chained aspects influenced by the way you create them.
The inner most aspect will need to be created last. The outer most aspect will need to be created first.
For this example, this is the order of aspect creation:
publicCacheAspectimplementsInvoker{ConcurrentMap<String,ConcurrentMap<Object,Object>>cache=newConcurrentHashMap<>();@OverridepublicObjectexecute(Methodmethod,Object[]args,Cacheann,RealInvokerrealInvoker)throwsThrowable{System.out.println("@@ Entering cache proxy with "+method.getName()+" "+args);ConcurrentMap<Object,Object>theCache=cache.get(method.getName());if(theCache==null){theCache=newConcurrentHashMap<>();cache.put(method.getName(),theCache);}Objectkey=args;if(args==null){key="";}Objectresult=theCache.get(key);if(result!=null){System.out.println("@@ Cache hit with "+method.getName()+" "+args);}else{System.out.println("@@ Cache missed with "+method.getName()+" "+args);result=realInvoker.invoke();theCache.put(key,result==null?"":result);}System.out.println("@@ Exiting cache proxy");returnresult;}}