Search code examples
mavenkotlinaopaspectjjcabi

AspectJ binary weaving with Jcabi Maven plugin not working for Kotlin code


I'm trying to run a little annotation over function that will log before and after the method execution.

What I've done: (all classes are under src/main/kotlin)

Annotation class

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogMe

Aspect class

import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect


@Aspect
abstract class Aspect {

    @Around("@annotation(LogMe) && execution(* *(..))")
    fun logMe(joinPoint: ProceedingJoinPoint): Any {
        beforeExecution(joinPoint)
        afterExecution(joinPoint)

        return joinPoint.proceed()
    }

    private fun beforeExecution(joinPoint: JoinPoint) {
        println("[${joinPoint.signature.name} has started its execution]")
    }

    private fun afterExecution(joinPoint: JoinPoint) {
        println("[${joinPoint.signature.name} has ended its execution]")
    }
}

Foo class with annotated method

class Foo {

    @LogMe
    fun yourMethodAround() {
        println("Executing foo.yourMethodAround()")
    }
}

main file

fun main(args: Array<String>) {
    val foo = Foo()
    foo.yourMethodAround()
}

my POM.xml (cut version)

...
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
            <version>1.3.40</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
            <version>1.3.40</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>

        <!-- TEST -->
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-test</artifactId>
            <version>1.3.40</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-test-junit</artifactId>
            <version>1.3.40</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src/main/kotlin</sourceDirectory>
        <testSourceDirectory>src/test/kotlin</testSourceDirectory>
        <plugins>
            <plugin>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <jvmTarget>1.8</jvmTarget>
                </configuration>
                <groupId>org.jetbrains.kotlin</groupId>
                <version>1.3.40</version>
                <executions>
                    <execution>
                        <id>kapt</id>
                        <goals>
                            <goal>kapt</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <goals> <goal>compile</goal> </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>test-compile</phase>
                        <goals> <goal>test-compile</goal> </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.jcabi</groupId>
                <artifactId>jcabi-maven-plugin</artifactId>
                <version>0.14.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>ajc</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>MainKt</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
...

When I basically run this main, what I'm obtaining is the println that it's into my Foo class method:

Executing foo.yourMethodAround()

But I'm not getting the before and after execution prinln that I was expecting from the Aspect class.

Does any of you ever faced this issue before? This is struggling me, because I can't understand what's going on here.


Solution

  • Disclaimer:

    • I have never used the Jcabi plugin before, normally I always use AspectJ Maven plugin, also for binary weaving.
    • I have never used the Kotlin language before, normally I use Java or Groovy.

    Now some things are not okay in your aspect:

    • It must not be abstract, otherwise no instance can be created.
    • For void methods it must be able to return null, so the Kotlin return type should be Any?
    • You should proceed() in between the before and after log messages, otherwise the log output will be wrong.
    • Assuming that your classes, especially the annotation class, do not reside in the default package but have an actual package name, you need to use the fully qualified class name in your pointcut, e.g. @annotation(de.scrum_master.app.LogMe)
    • Using an aspect class name Aspect, i.e. the same name as the @Aspect annotation, just in another package, is kind of ugly. You should rename it.

    For me this works nicely:

    package de.scrum_master.aspect
    
    import org.aspectj.lang.JoinPoint
    import org.aspectj.lang.ProceedingJoinPoint
    import org.aspectj.lang.annotation.Around
    import org.aspectj.lang.annotation.Aspect
    
    @Aspect
    class LogAspect {
      @Around("@annotation(de.scrum_master.app.LogMe) && execution(* *(..))")
      fun logMe(joinPoint: ProceedingJoinPoint): Any? {
        beforeExecution(joinPoint)
        val result = joinPoint.proceed()
        afterExecution(joinPoint)
        return result
      }
    
      private fun beforeExecution(joinPoint: JoinPoint) {
        println("[${joinPoint.signature.name} has started its execution]")
      }
    
      private fun afterExecution(joinPoint: JoinPoint) {
        println("[${joinPoint.signature.name} has ended its execution]")
      }
    }
    

    Besides, maybe you also should configure the Jcabi plugin to language level Java 8. It works without it here, but maybe it is better depending on which language features you use:

    <configuration>
      <source>1.8</source>
      <target>1.8</target>
    </configuration>
    

    My console after mvn clean verify looks like this:

    $ java -jar target/so-aj-kotlin-56890630-1.0-SNAPSHOT.jar
    [yourMethodAround has started its execution]
    Executing foo.yourMethodAround()
    [yourMethodAround has ended its execution]
    

    My IDE IntelliJ IDEA does not quite pick up the binary weaving stuff because it does not know Jcabi, only AspectJ Maven. So I just configured the project to delegate compilation to Maven:

    Delegate IDEA build to Maven

    Then the log output is the same when running the application from IDEA directly.