Adam Warski

1 Oct 2013

MacWire 0.5: Interceptors

dependency injection
macwire
modularity
scala

Interceptors are very useful for implementing cross-cutting concerns. Classic use-cases include security, logging or transaction support. Since version 0.5, MacWire contains an implementation of interceptors which can be applied to arbitrary object instances in a Scala-friendly way, and which plays nicely with the traits-as-modules approach. No compile-time or load-time bytecode manipulation is required; only javassist is used at run-time to generate a proxy.

MacWire defines an Interceptor trait, which has just one method: apply. When applied to an object, it should return an intercepted instance.

Suppose we have a couple of objects, and we want each method in these objects to be surrounded by a transaction. The objects are defined and wired inside a trait (could also be inside a class/object). That’s of course a perfect fit for an interceptor; we’ll call the interceptor transactional. We will also make it abstract so that we can swap implementations for testing, and keep the interceptor usage declarative:

trait BusinessLogicModule {
  // not intercepted
  lazy val balanceChecker = new BalanceChecker()

  // we declare that the usage of these objects is transactional
  lazy val moneyTransferer = transactional(new MoneyTransferer())
  lazy val creditCard = transactional(new CreditCard())

  // abstract interceptor
  def transactional: Interceptor
}

MacWire provides two interceptor implementations:

  • ProxyingInterceptor – proxies the given instance, and returns the proxy. A provided function is called on invocation
  • NoOpInterceptor – useful for testing, when applied returns the instance unchanged

A proxying interceptor can be created in two ways: either by extending the ProxyingInterceptor trait, or by passing a function to the ProxyingInterceptor object. For example:

object MyApplication extends BusinessLogicModule {
  lazy val tm = new TransactionManager()

  // Implementing the abstract interceptor
  lazy val transactional = ProxyingInterceptor { ctx =>
    // This function will be called when a method on the intercepted
    // object is invoked

    try {
      // Using objects (dependencies) defined in the application
      tm.begin()
      // Proceeding with the invocation: calls the original method
      val result = ctx.proceed()
      tm.commit()

      result
    } catch {
      case e: Exception => {
        tm.rollback()
        throw e
      }
    }
  }
}

The ctx instance contains information on the invocation, such as the method being called, the parameters or the target object. Another example of an interceptor, which uses this information, is a TimingInterceptor, defined in the trait-extension style:

object TimingInterceptor extends ProxyingInterceptor {
  def handle(ctx: InvocationContext) = {
    val classWithMethodName = s"${ctx.target.getClass.getSimpleName}.${ctx.method.getName}"
    val start = System.currentTimeMillis()
    println(s"Invoking $classWithMethodName...")
    try {
      ctx.proceed()
    } finally {
      val end = System.currentTimeMillis()
      println(s"Invocation of $classWithMethodName took: ${end-start}ms")
    }
  }
}

You can see this interceptor in action in the MacWire+Scalatra example, which also uses scopes and the wire[] macro. Just explore the code on GitHub, or run it by executing sbt examples-scalatra/run after cloning MacWire and going to http://localhost:8080.

Interceptors can be stacked (order of interceptor invocation is simply ordering of declarations – no XML! :) ), and combined with scopes.

Note that although the code above does not use wire[] for instance wiring, interceptors of course work in cooperation with the wire[] macro. The interceptors can be also used stand-alone, without even depending on the macwire-macros artifact.

The interceptors in MacWire correspond to Java annotation-based interceptors known from CDI or Guice. For more general AOP, e.g. if you want to apply an interceptor to all methods matching a given pointcut expression, you should use AspectJ or an equivalent library.

If you’d like to try MacWire, just head to the GitHub project page, which contains all the details on installation and usage.

What do you think about such an approach to interceptors?

Adam

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?