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.
“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.
In AOP, you have a few key parts:
@Component class PerformACommand { @Logged fun execute(input: String): String { return "this is a result for $input" } }
@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'") } }
@Target(AnnotationTarget.FUNCTION) annotation class Logged
@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.”
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?
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.
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.
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.
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.
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") }
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.
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.
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.
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.
If you would like to be a guest contributor to the Stackify blog please reach out to stackify@stackify.com