Spring AOP Tutorial With Examples

Mark Henke Developer Tips, Tricks & Resources

You may have heard of aspect-oriented programming, or AOP, before. Or maybe you haven’t heard about it but have come across it through a Google-search rabbit hole. You probably do use Spring, however. So you’re probably curious how to apply this AOP to your Spring application.

In this article, I’ll show you what AOP is and break down its key concepts with some simple examples. We’ll touch on why it can be a powerful way of programming and then go into a contrived, but plausible, example of how to apply it in Spring.  All examples will be within a Spring application and written in JVM Kotlin, mainly because Kotlin is one of my favorite useful languages.

Quick Description of AOP

“Aspect-oriented programming” is a curious name. It comes from the fact that we’re adding new aspects to existing classes. It’s an evolution of the decorator design pattern. A decorator is something you hand-code before compiling, using interfaces or base classes to enhance an existing component. That’s all nice and good, but aspect-oriented programming takes this to another level. AOP lets you enhance classes with much greater flexibility than the traditional decorator pattern. You can even do it with third-party code.

The Parts of Spring AOP

In AOP, you have a few key parts:

  • Core component. This is the class or function you want to alter. In Spring AOP, we’re always altering a function. For example, we may have the following command:
  • @Component
    class PerformACommand {
        @Logged
        fun execute(input: String): String {
            return "this is a result for $input"
        }
    }
    

  • Aspect. This is the new logic you want to add to the existing class, method, sets of classes, or sets of methods. A simple example is adding log messages to the execution of certain functions:
  •  

    @Aspect
    @Component
    class LoggingAspect {
    
        @Around("@annotation(Logged)")
        fun logMethod(joinPoint: ProceedingJoinPoint) {
            var output = joinPoint.proceed()
            println("method '${joinPoint.signature}' was called with input '${joinPoint.args.first()}' and output '$output'")
        }
    }
    
    • JoinPoint. OK, now the terms get weird. A JoinPoint is the place within the core component that we’ll be adding an aspect. I’m putting this term here mainly because you’ll see it a lot when researching AOP. But for Spring AOP, the JoinPoint is always a function execution. In this example, it will be any function with an “@Logged” annotation: 
    @Target(AnnotationTarget.FUNCTION)
    annotation class Logged
    • Pointcut. The pointcut is the logic by which an aspect knows to intercept and decorate the JoinPoint. Spring has a few annotations to represent these, but by far the most popular and powerful one is “@Around.” In this example, the aspect is looking for the annotation “Logged” on any functions. 
    @Around("@annotation(Logged)")

    If you wire the example code up to a Spring application and run:

    command.execute("whatever")

    You’ll see something like this in your console: “method ‘String com.example.aop.PerformACommand.execute(String)’ was called with input ‘whatever’ and output ‘this is a result for whatever’

    Spring AOP can achieve this seeming magic by scanning the components in its ApplicationContext and dynamically generating code behind the scenes. In AOP terms, this is called “weaving.”

    green leafed plant in pot

    Why AOP Is Useful

    With that explanation and examples providing understanding, let’s move on to the favorite part for any programmer. That’s the question “why?” We love this question as developers. We’re knowledge workers who want to solve problems, not take orders. So, what problems does AOP solve in Spring? What goals does it help one achieve?

    Quick Code Reuse

    For one thing, adding aspects lets me reuse code across many, many classes. I don’t even have to touch much of my existing code. With a simple annotation like “Logged,” I can enhance numerous classes without repeating that exact logging logic.

    Although I could inject a logging method into all these classes, AOP lets me do this without significantly altering them. This means I can add aspects to my code in large swaths quickly and safely.

    Dealing With Third-Party Code

    Let’s say normally I want to inject shared behavior into a function that I then use in my core components. If my code is proved by a third-party library or framework, I can’t do that! I can’t alter the third-party code’s behavior. Even if they’re open source, it’ll still take time to understand and change the right places. With AOP, I just decorate the needed behavior without touching the third-party code at all. I’ll show you exactly how to do that in Spring with the blog translator example below.

    Cross-Cutting Concerns

    You’ll hear the term “cross-cutting concerns” a lot when researching AOP. This is where it shines. Applying AOP lets you stringently use the single responsibility principle. You can surgically slice out the pieces of your core components that aren’t connected to its main behavior: authentication, logging, tracing, error handling, and the like. Your core components will be much more readable and changeable as a result.

    Example: A Blog Translator

    Although I showed snippets of a logging aspect earlier, I want to walk through how we might think through a more complex problem we might have, and how we can apply Spring AOP to solve it.

    As a blog author, imagine if you had a tool that would automatically check your grammar for you and alter your text, even as you write! You download this library and it works like a charm. It checks grammar differently based on what part of the blog post you’re on: introduction, main body, or conclusion. It heavily encourages you to have all three sections in any blog post.

    You’re humming along, cranking out some amazing blog posts, when a client commissions a request: can you start translating your blogs to German to reach our German audience better? So you scratch your head and do some research. You stumble upon a great library that lets you translate written text easily. You tell the client, “Yes, I can do that!” But now you have to figure out how to wire it into your grammar-checking library. You decide this will be a great case to try out Spring AOP to combine your grammar tool with this translation library.

    MacBook Pro beside plant in vase

    Wiring It Up

    First, we want to add the Spring AOP dependency to our Spring Boot project. We have a “build.gradle” file to put this into:

    dependencies {
     implementation("org.springframework.boot:spring-boot-starter")
     implementation("org.springframework.boot:spring-boot-starter-aop")
    }
     

    Analyzing Our Core Components

    Before we implement anything, we take a close look at our tool codebase. We see that we have three main components, one for each section of a blog post:

    class IntroductionGrammarChecker {
        fun check(input: BlogText): BlogText {
           ...
        }
    }
    
    class MainContentGrammarChecker {
    
        fun check(input: BlogText): BlogText {
           ...
        }
    }
    
    class ConclusionGrammarChecker {
        fun check(input: BlogText, author: Author): BlogText {
            ...
        }
    }

    Hmm…it looks like each one produces the same output: a BlogText. We want to alter the output of each of these checkers to produce German text instead of English. Looking closer, we can see that they all share the same signature. Let’s keep that in mind when we figure out our pointcut.

    The Core Logic

    Next, let’s bang out the core logic of our aspect. It’ll take the output of our core component, send it through our translator library, and return that translated text:

    @Aspect
    @Component
    class TranslatorAspect(val translator: Translator) {
    
        @Around("execution(BlogText check(BlogText))")
        fun around(joinPoint: ProceedingJoinPoint): BlogText {
            val preTranslatedText = joinPoint.proceed() as BlogText
            val translatedText = translator.translate(preTranslatedText.text, Language.GERMAN)
            return BlogText(translatedText)
        }
    }

    Note a few things here. First, we annotate it with “@Aspect.” This cues Spring AOP in to treat it appropriately. The “@Component” annotation Spring Boot will see it in the first place.

    We also use the “@Around” pointcut, telling it to apply this aspect to all classes that have a method signature of “check(BlogText): BlogText.” There are numerous different expressions we can write here. See this Baeldung article for more. I could’ve used an annotation, like the “@Logged” above, but this way I don’t have to touch the existing code at all! Very useful if you’re dealing with third-party code that you can’t alter.

    The method signature of our aspect always takes in a ProceedingJoinPoint, which has all the info we need to run our aspect. It also contains a “proceed()” method, which will execute the inner component’s function. Inside the function, we proceed with the core component, grabbing its output and running it through the translator, just as we planned. We return it from the aspect, with anything that uses it being none the wiser that we just translated our text to German.

    A Trace of Something Familiar

    Now that you’re familiar with Spring AOP, you may notice something about the “@Logged” annotation. If you’ve ever used custom instrumentation for Java in Retrace, you may notice it looks a lot like the “@Trace” annotation.

    The similarity of “@Logged” to “@Trace” is not by coincidence. “@Trace” is a pointcut! Although Retrace does not use spring AOP per se, it does apply many AOP principles into how it lets you configure instrumentation.

    The Final Aspect

    We’ve only touched the surface of AOP in Spring here, but I hope you can still see its power. Spring AOP gives a nonintrusive way of altering our components, even if we don’t own the code for that component! With this, we can follow the principles of code reuse. We can also implement wide-sweeping, cross-cutting concerns with just a few lines of code. So, find a place in your Spring application where this can bring value. I highly recommend starting with something like “@Logged” or “@Trace” so you can easily measure and improve your system performance.

    About Mark Henke

    This post was written by Mark Henke. Mark has spent over 10 years architecting systems that talk to other systems, doing DevOps before it was cool, and matching software to its business function. Every developer is a leader of something on their team, and he wants to help them see that.