Custom Annotation Processing with Spring Boot

Nikasakana
5 min readNov 14, 2020

--

Photo by Clément H on Unsplash

General

Hi there. In this article i want to share with you one of the coolest(i mean reeaaally cool) technique that might benefit your Spring applications.

The main topic, I’ll try to introduce, is how to create, process and get benefits from using custom annotations in Spring framework based Java applications. By getting a benefit, i mean giving different components additional/desired functionalities just by, simply, adding annotations to them.

In this article we’ll get our hands dirty with creating a new annotation “@Profile”. The desired functionality of it is to, display bean’s method profiling statistics, annotated with it.

FYK, profiler, in this context, is a piece of code that tells us how long did it take for a function to complete.

We will only scratch the surface of all the technologies used in this example like: Annotations, JDK Proxies, Java Reflection, BeanPostProcessors of Spring and etc. Since i want to concentrate mainly on different techniques used here and not on the exact technologies.

Started Digging into Annotations

If you visit Oracle’s official website and try to get detailed information about annotations or their usage, you will be greeted by a definition, that will be more or less like this:

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

So basically saying, if you want to get programmatic benefits from annotations, only adding them isn’t enough. There should be some kind of an “Annotations’ Processor” that upon inspecting annotated class will say:

hmm, as i see this class has an @X annotation, let’s exchange this annotated part of the class with this chunk of code so it gets it’s needed functionality.

Let me remember how to do it…

Oh right, i’ll wrap this object with a Proxy object. This proxy will delegate function calls to the real object, meanwhile before delegating, i’ll be able to add custom functionality to it…. huh that’s so cool.

Getting to know Spring “Bean Post Processors”

In the Spring framework the actual component that is able to use the “proxy logic”, stated above, is called “Bean Post Processor”.

In the next articles, about “Spring bean’s life-cycle”, I’ll talk about them in much more details, but simply saying, bean post processors are infrastructure components of Spring that have the accessability to all the beans that Spring framework instantiates, in a way that all beans should pass through them. So with BPP-s we, without a doubt, know that, at some point in time, during the instantiation, all beans would have gone through them. Now the interesting part is, since all beans will pass through the methods of these processors, how can we use them to add new functionality only to those beans that will have specified annotation?

Let’s see it in the code.

“Bean Post Processor” internals

For using custom BPP, you must implement BeanPostProcessor interface and override it’s two phases :

  • Before Initialization phase
  • After Initialization phase

I won’t get into much details of all the differences between these two (it’s the topic for next article), but the basic difference is that Before Initialization and After Initialization methods are called accordingly before and after Bean’s “Init-method” is called.

One, very important, fact/convention, Spring dictates, is that the beans returned from the “Before Initializaiton” phase should be actual/original beans passed to “Before Initialization” method. Proxies are wrapped around beans only in “After Initialization” phase, so we should also respect this convention and in case of usage of any custom “proxy wrapping”.

Implementation Step

Now let’s discuss what should we do.

Let’s for a moment assume that we correctly get all the beans/Classes annotated with annotation we want to work with.

What are we going to do next?

We are going to use JDK Dynamic proxies to wrap these beans and then, instead of the real bean, return proxy to the client. Client will have no idea that it isn’t a real bean, since the same interfaces are used.(we assume that client refers to all the beans using it’s interfaces).

  • So in “Before Initialize” step we’ll analyze which beans are annotated with @Profile annotation, using Java Reflection package, and fill up a simple Java Map with them. So this way we can, in the later phase, wrap proxies around them, according to spring conventions.
  • In After Initialize step we can, by respecting common conventions, wrap needed spring beans in previously filled Java Map.

As already stated, here we use JDK Dynamic proxy. Which generates proxy classes of some reference class on the fly, using their interfaces.From the code it’s pretty easy to figure out that the core logic of the “@Profile” proxy is to get time difference between the start and the end of method invocation and then present it to the user.

Bootstrapping and Testing @Profile

For the last step let’s test and make sure that beans annotated with @Profile annotation are really profiled and user get’s the information about methods’ invocation times.

Components we use for testing:

— “ ConcreteTestBean”

  • Is a nested(for the sake of simplicity) spring bean.
  • Has, our custom,“@Profile” annotation on it.
  • Implements “InterfaceForTestBean”, which is needed for the correct functionality of JDK proxies.

— “Bootstrapper”

  • Is used to bootstrap methods upon Spring Application startup.
  • We inject InterfaceForTestBean — bean in here.
  • Spring behind the scenes automagically finds the concrete implementation of this bean and delegates method calls to it.

Output upon the application startup

we can see on the last line of console logs that time duration of method’s invocation was displayed. And all thanks to our custom @Profile annotation.

“yay!”

finalize()

As you saw Bean Post Processors can be used in variety of ways for tuning spring beans and making them more functional and fun to work with. I personally think, that using proxies, the possible cases to tune your beans are only limited by your imagination and creativity. So let’s enjoy working with them :)

--

--