DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Dependency Injection in Spring
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Smart Dependency Injection With Spring: Assignability (Part 2 of 3)
  • Introduction to Apache Kafka With Spring

Trending

  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Agentic AI for Automated Application Security and Vulnerability Management
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Ethical AI in Agile
  1. DZone
  2. Coding
  3. Languages
  4. Spring Annotation Processing: How It Works

Spring Annotation Processing: How It Works

If you see an annotation, there must be some code somewhere to process it.

By 
Alan Hohn user avatar
Alan Hohn
·
May. 02, 16 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
62.8K Views

Join the DZone community and get the full member experience.

Join For Free

One of the things I emphasize when I teach Java classes is the fact that annotations are inert. In other words, they are just markers, potentially with some properties, but with no behavior of their own. So whenever you see an annotation on a piece of Java code, it means that there must be some other Java code somewhere that looks for that annotation and contains the real intelligence to do something useful with it.

Unfortunately, the issue with this line of reasoning is that it can be pretty difficult to identify exactly which piece of code is processing the annotation, particularly if it is inside a library. And code that processes annotations can be confusing, as it uses reflection and has to be written in a very generic way. So I thought it would be worthwhile to look at an example that's done well to see how it works.

I'm going to walk through the InitDestroyAnnotationBeanPostProcessor from the Spring framework to show how it works. I've chosen this one because it's relatively simple as these things go, it does something that's relatively easy to explain, and I happened to need it recently for some work I was doing.


Spring Bean Post Processing

First I would like to start with a little explanation of the purpose of Spring. One of the things the Spring framework does is "dependency injection". This changes the way we typically tie together modules within a piece of code. For example, let's say that we've written some application logic that needs a connection to the database. Rather than coding into the application logic the specific class that provides that connection, we can just express it as a dependency, either in the constructor or a setter method:


class MyApplication {

    private DataConnection data;

    ...
    public void setData(DataConnection data) {
        this.data = data;
    }
    ...
}


Of course, we can do this dependency injection ourselves, and we might want to if we're writing a simple library and want to avoid adding a dependency to Spring. But if we're wiring together a complicated application, Spring can be very handy.

Since there's no magic, if we're going to let Spring inject these dependencies for us, there's going to be a tradeoff. Spring is going to have to "know" about the dependencies and about the classes and objects in our application. The way Spring prefers to handle this is by allowing Spring to do the instantiation of the objects; then it can keep track of them in a big data structure called the application context.


Post Processing and Initialization

And here's where InitDestroyBeanPostProcessor comes in. If Spring is going to handle instantiation, there are going to be cases where some "extra work" needs to be done after that instantiation is done, but before the application can start its real processing. One piece of "extra work" that needs doing is calling objects to tell them when they've been fully set up, so they can do any extra initialization they need. This is especially important if we use "setter" injection, as above, where dependencies are injected by calling setXxx() methods, because those dependencies won't be available at the time the object's constructor is called. So Spring needs to allow users to specify the name of some method that should be called after the object has been initialized.

Spring has always supported using XML to define the objects that Spring should instantiate, and in that case there was an'init-methodattribute that could be used to specify the method. Obviously in that case it still needed reflection to actually look up and call the method. But since annotations became available in Java 5, Spring has also supported tagging methods with annotations to identify them as objects that Spring should instantiate, to identify dependencies that should be injected, and to identify initialization and destruction methods that should be called.

That last item is handled by the InitDestroyBeanPostProcessor or one of its subclasses. A post processor is a special kind of object, instantiated by Spring, that implements a post processor interface. Because it implements this interface, Spring will call a method on it with each object Spring has instantiated, allowing it to modify or even replace that object. This is part of Spring's approach to a modular architecture, allowing easier extension of capability.


How It Works

It so happens that JSR-250 identified some "common" annotations, including a @PostConstruct annotation that is designed to tag initialization methods, and a @PreDestroy annotation for destruction methods. However, InitDestroyBeanPostProcessor is designed to work with any set of annotations, so it provides methods to identify the annotations:

    public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
        this.initAnnotationType = initAnnotationType;
    }
...
    public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
        this.destroyAnnotationType = destroyAnnotationType;
    }


Note that these are ordinary setter methods, so this object can itself be set up using Spring. In my case, I was using Spring'sStaticApplicationContext, as I've described previously.

Once Spring has instantiated the various objects and has injected all of the dependencies, it calls the postProcessBeforeInitializationmethod on all the post processors, for every object. This gives the post processor a chance to modify or replace the object before it's initialized. Because dependencies have been injected, this is the place where InitDestroyAnnotationBeanPostProcessor calls the initialization method.

    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        metadata.invokeInitMethods(bean, beanName);
    }


Since we're interested in how the code deals with annotations, we're interested in findLifecycleMetadata(), since that's where the class is inspected. That method checks a cache, which is used to avoid performing reflection more than necessary, since it can be expensive. If the class hasn't been inspected yet, the method buildLifecycleMetadata() is called. The meat of this method looks like:

ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
    @Override
    public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
        if (initAnnotationType != null) {
            if (method.getAnnotation(initAnnotationType) != null) {
                LifecycleElement element = new LifecycleElement(method);
                currInitMethods.add(element);
            }
        }
        ...
    }
});


The ReflectionUtils is a handy class that simplifies using reflection. Amongst other things, it converts the numerous checked exceptions that go along with reflection into unchecked exceptions, making things easier. This particular method iterates over only local methods (i.e. not methods that are inherited) and calls the callback for each method.

After all of that setup, the part that checks for the annotation is pretty boring; it just calls a Java reflection method to check for the annotation and, if it's found, stores that method away as an initialization method.

Wrap Up

The fact that ultimately what's happening here is simple is really the point that I try to make when I teach reflection. It can be challenging to debug code that uses annotations to control behavior, because from the outside it's pretty opaque, so it's hard to envision what is happening (or not happening) and when. But at the end of the day, what's happening is really just Java code; it might not be immediately apparent where that code is, but it's there.


Spring Framework Annotation Processing Object (computer science) Dependency injection Debug code application

Opinions expressed by DZone contributors are their own.

Related

  • Dependency Injection in Spring
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Smart Dependency Injection With Spring: Assignability (Part 2 of 3)
  • Introduction to Apache Kafka With Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!