Dynamically proxying annotations to avoid inducing API dependency

Annotations are API, and as API they need to be on the classpath at compile time. And usually, you do not want your (portable) business code to depend on yet another API.

In this article, I will present you how to allow your library users not to depend on the library API just for the annotations you provide, using java.lang.reflect and its proxy facilities.

My examples will be based on Ehcache source code, since it is for the @IgnoreSizeOf annotation that this whole idea of annotation proxying came up ! (for reference, @IgnoreSizeOf is the annotation used by Ehcache to filter out classes from the SizeOf computation)
Doing this on the Ehcache @IgnoreSizeOf annotation will allow framework developers, who embed Ehcache in their framework (and want to benefit from the sizeOf feature), to not introduce a dependency on Ehcache for their users.

Our use case : you want to use the original annotation but not to depend on it.

Instead of using and depending on Ehcache’s @IgnoreSizeOf (net.sf.ehcache.pool.sizeof.annotations.IgnoreSizeOf) :

you want to annotate your classes with your custom com.myproject.ehcache.IgnoreSizeOf annotation , which of course has no relationship (well, it looks like it, acts like it; but it is not the same, see this as duck typing) with the original @IgnoreSizeOf annotation :

You can notice that the inherited method is missing in the custom annotation, more on this in the next chapter.

The big picture : use java.lang.proxy to transform the type of your annotation

Once you have scanned your class or field or package (any AnnotatedElement) for your custom annotation :

It is as simple as writing those 2 lines of code :

Well not exactly… all the interesting code is actually in the InvocationHandler, that will redirect method calls to our custom annotation methods or, if it can’t, to the reference annotation methods.

One method to implement here : the invoke method, called on our proxied annotation every time we call a method on it.
If the method called on the proxy matches a method in our custom annotation, it just delegates the call to it.
But if not (for example remember our custom annotation does not have the inherited() method, but the reference one has), and this is where the JDK annotations are cool, the reference annotation default return value will be returned instead.
What if there is no default value in the reference annotation ? In that case you need to provide this method in your custom annotation (with a default or you can force your consumer to provide a value for this method).

In this example the consumer of Ehcache does not even need to write a complete annotation to mimic the original one ! (since the only method in the original annotation has a default value)

Actually, even in the Oracle JDK they use proxying to resolve annotations, in sun.reflect.annotation.AnnotationParser

using their own sun.reflect.annotation.AnnotationInvocationHandler

Conclusion

Even if this code was written specifically with Ehcache @IgnoreSizeOf annotation in mind, it is generic enough for any framework authors to use (even more since it is Apache 2 licensed)

You can checkout Ehcache source code from http://svn.terracotta.org/svn/ehcache/trunk/ehcache/ehcache-core/ and look for AnnotationSizeOfFilter and AnnotationProxyFactory (or, even better, the test class : FilteredSizeOfTest) to learn more detail on how to provide a mechanism for letting your consumer not depend on your annotations !

Special thanks to Alex Snaps and Chris Dennis for their guidance !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *