AOP and Kotlin


#1

I would like to use Kotlin in a real world project, interestingly using the Ninja framework mentioned a few threads down, but noticed AOP support doesn't appear to be working.  Ninja framework uses standard libraries: Guice and Guice Persist (which uses aopalliance for AOP support).  So for example, Kotlin methods marked Transactional are not starting transactions.

I was able to reproduce this in a simpler test case using Kotlin and the AOP example from the Guice docs: https://code.google.com/p/google-guice/wiki/AOP

public class KotlinSimple {


  NotOnWeekends
  fun hello() {
  println(“Hello Kotlin”)
  }
}

The NotOnWeekends annotation has no effect here. Any help would be great.

Thanks!


#2

I'm not really familiar with how Guice does AOP. Does it weave aspects at load time or at compilation time? How is your project set up so that AOP knows what classes are subject to weaving?


#3

>> Does it weave aspects at load time or at compilation time?

I’m fairly certain it does this at runtime.

>> How is your project set up so that AOP knows what classes are subject to weaving?

Guice provides a bindInterceptor(…) method to bind any classes annotated with annotation X with MethodInterceptor Y.  So my test example is directly from the Guice AOP docs (https://code.google.com/p/google-guice/wiki/AOP).

public class AopModule extends AbstractModule {
  protected void configure() {
  bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class),
           new WeekendBlocker());
  }
}

Unfortunately it’s no longer the weekend, so we can modify WeekendBlocker class from the Guice-AOP docs to always throw an exception:

public class WeekendBlocker implements MethodInterceptor {
  public Object invoke(MethodInvocation invocation) throws Throwable {
  throw new IllegalStateException(invocation.getMethod().getName() + " not allowed on weekends!");
  }
}

Here’s the main method which executes the test:

public class AopApp {


  public static void main(String[] args) {
  Injector injector = Guice.createInjector(new AopModule());
//   Simple simple = injector.getInstance(Simple.class); // java
  KotlinSimple simple = injector.getInstance(KotlinSimple.class);
  simple.hello();
  }
}

And KotlinSimple source:

public class KotlinSimple {


  NotOnWeekends
  fun hello() {
  println(“Hello Kotlin”)
  }
}

Guice-Persist @Transactional annotations are bound similarly (though this is done for you by including JpaPersistModule):

  // class-level @Transacational
  bindInterceptor(annotatedWith(Transactional.class), any(), getTransactionInterceptor());
  // method-level @Transacational
  bindInterceptor(any(), annotatedWith(Transactional.class), getTransactionInterceptor());

See here: https://code.google.com/p/google-guice/source/browse/extensions/persist/src/com/google/inject/persist/PersistModule.java


#4

Could you attach to sample project? Thanks


#5

Sure.  I attached a Maven/IntelliJ project which shows a simple annotation and an example using Guice Persist.  For both examples I provide a Java and Kotlin version.

Also, in pom.xml, you’ll see there’s a 4.0-beta version commented out for Guice and Guice Persist - the problem exists with both versions.



kotlinaop.zip (24.5 KB)

#6

Sorry for not replying sooner. You were right that Guice implements aspects at runtime. At least for the cases you sent in attached project Injector.getInstance() returns generated subclasses with the overriden methods containing injected bytecode. But it can't do that for Kotlin because unlike Java, subclassing and overriding is forbidden in Kotlin for classes and functions by default. So the solution is to add open keyword into Kotlin code:

public open class SimpleKotlin {   AlwaysThrowException   open fun hello() {   println("Hello, Kotlin")   } }

public open class PersistKotlin [Inject] (val em : EntityManager) {   Transactional   open fun hello() {   println("Kotlin: In Transaction? ${em.getTransaction()?.isActive()}")

    val p = Person()
    p.name = "Kotlin"
    em.persist(p)

  }
}


Or you can emulate the same bug with lack of aspects behaviour in Java by adding final:

public final class SimpleJava {   @AlwaysThrowException   public final void hello() {   System.out.println("Hello, Java");   } }

Interesting thing, that if class is final, but method is not, Guice provide a very helpfull message that it can't subclass a particular class.


#7

Oh wow, thank you.  It didn't even occur to me to try that.

And now, after taking another look at the Guice AOP documentation, it clearly states:

  • Classes must be public or package-private
  • Classes must be non-final
  • Methods must be public, package-private or protected
  • Methods must be non-final
  • Instances must be created by Guice by an @Inject-annotated or no-argument constructor

Thank you for looking into this.  If anyone is curious, I tested the Transactional annotation with the Ninja framework and it works perfectly fine given the changes mentioned above by Nikolay.