Aspect Oriented Programming: Spring, Kotlin, Gradle
in Blog
I’ve seen a lot of resources regarding AOP for Java and / or Maven, but couldn’t find a lot about the Kotlin+Gradle combo. As for the Spring side, the Spring Initializr doesn’t even list the aop starter, which is apparently by design. So I decided to show a working solution for the mentioned stack. The code referenced can be found here.
Aspect oriented programming is a great way of defining cross-cutting concerns that can simplify your project and help establish your architecture.
Setup
First thing is to add the required dependencies to the build.
- The spring aop starter is needed for defining aspects.
- The aspectj plugin is needed for compile-time checks.
There are a bunch of gradle plugins, but most are outdated and lack proper documentation. The freefair plugin was the only one I could get to work, documentation here. Just add the post-compile-weaving plugin and set your aspectj version in gradle.properties
.
Aspects
Now we can start writing a custom aspect. The example shows an aspect that monitors all activities by logging start and end events including the time elapsed and whether it succeeded or failed. Using @Around
advice we can wrap all methods that match the defined pointcut in a try-catch-finally.
@Aspect
@Component
class ActivityAspect {
private val logger = LoggerFactory.getLogger(this::class.java)
@Around("Architecture.activity() || Architecture.repository() || Architecture.service()")
fun monitorActivity(proceedingJoinPoint: ProceedingJoinPoint): Any? {
val name = proceedingJoinPoint.staticPart.signature.toShortString()
var state = "SUCCESS"
logger.info("Activity start: $name")
val startTime = System.currentTimeMillis()
try {
return proceedingJoinPoint.proceed()
}
catch (ex: Exception) {
state = "ERROR"
throw ex
}
finally {
val timeElapsed = System.currentTimeMillis() - startTime
logger.info("Activity end: $name ($state $timeElapsed ms)")
}
}
}
Pointcuts
In order to define what methods the advice is applied to, pointcuts can be used to describe the architecture of the application. In the example we want to monitor all public methods in any service or repository, as well as any method annotated with a custom @Activity
annotation.
class Architecture {
@Pointcut("execution(* (@org.springframework.stereotype.Repository *).*(..))")
fun service() = Unit
@Pointcut("execution (* (@org.springframework.stereotype.Service *).*(..))")
fun repository() = Unit
@Pointcut("(@annotation(Activity) && execution(* *(..)))")
fun activity() = Unit
}
Here is a demo service that would match these pointcuts:
@Service
class DemoService {
@Activity // optional, can be used on methods outside of services / repositories
fun run(someBool: Boolean): String {
...
}
fun noAnnotation() {
...
}
}
Compile-time checks
AspectJ can be used to define compile-time checks that produce errors or warnings when matched. For example, we can introduce a check that ensures that no code from the java.sql
or javax.sql
package is called outside of a repository.
@Aspect
class CheckAspect {
@DeclareError("!Architecture.repository() && (call (* java.sql..*.*(..)) || call (* javax.sql..*.*(..)))")
val jdbcOutsideRepository = "Only call jdbc code from within repositories."
}
When compiling the following function, the compiler will throw an error.
fun methodUsingJavaxSql() {
javax.sql.rowset.RowSetMetaDataImpl().getCatalogName(2)
}
I am really happy with the results and the fact that the Spring team is pushing and actively promoting Kotlin as the language of choice. One issue that still remains is handling suspending functions with AOP, since proceed
will return when suspending. Hopefully the fix makes it into the next release. Tracking issue here.