Cross Cutting Concerns in Scala


Although we can use powerful functional and object oriented languages like Scala, I think that Aspect Oriented Programming (AOP) is still one important part of software development. This article should show how easy to use AOP is.

I can not write a better definition of CCC than:

In my point of view some of the main points are:

  • CCC are both detachable and cross-cutting features of an application
  • CCC do not fit cleanly into functional programming, object-oriented programming or procedural programming (Symptom: “Code scattering – when a concern is spread over many modules and is not well localized and modularized”).
  • DRY principle: don’t repeat yourself

examples are:

  • Tracing (not logging)
  • (DB Transaction) Scope Wrapper
  • Privilege Checks
  • Performance measurements etc.

Stackable Traits

(Stackable Trait pattern see here)
(Approach based on Jonas Boners Blogpost here)

Java has “codeless” Interfaces. Scala has rich interfaces called Traits. Although they contain code, the main difference to multiple inheritance is the interpretation of super(). Using more than one Trait per Class means, that a trait’s method call can be stacked for all implementations of the same method name.
Nevertheless, this can not be a good Crosscutting Concern pattern, because we still are only able to overwrite known methods.

Therefore it is required to use some kind of a search string to define where (that’s called Pointcut) to insert the code (that’s called Advice). Jonas Boner uses Stackable Traits and the AspectJ weaver matcher (and therefore the AspectJ matcher syntax) to provide that.
I tweaked the code a little bit to provide a more AspectJ like structure.

The final code looks like this:

val aspect = new Aspect("execution(* *.bar(..))")
              with InterceptBefore
              with InterceptorAround
              with InterceptAfter
val foo = aspect.create[Foo](new FooImpl)
foo.bar("bar")

Although tweaked the basic idea is adopted.

  • Create Traits that are stacking its invoke() methods. In this case the invoke() method is now divided into before(), around() and after() interceptor methods.
  • Traits are implementing this methods with their Crosscutting Concern code
  • Use a factory to create “managed” objects (foo). The factory creates a proxy object that allows to intercept the method call to the original object.
  • inside the interception code the AspectJ matcher is used to check if this method call fits to the given Pointcut pattern.

Main Interceptor Trait where the before, around and after methods will be overwritten by the corresponding Interceptor implementations:

abstract trait InterceptorInvoker extends Interceptor {
 
  def before: AnyRef = null
  def after(result: AnyRef): AnyRef = result
  def around(invoke: => AnyRef): AnyRef = invoke
 
  abstract override def invoke(invocation: Invocation): AnyRef =
    if (matches(pointcut, invocation)) {
      before
      val result = around(super.invoke(invocation))
      after(result)
    } else
      super.invoke(invocation)
}

More Code:

The Aspect class and the Interceptor Traits are here
The Test Spec is here, which prints the following output:

=====> interceptor begin
msg: foo
=====> interceptor commit
=====> Enter Around Aspect
msg: bar
=====> Exit Around Aspect
 
msg: foo
=====> Enter Before Aspect
msg: bar
=====> Enter After Aspect with result: null
 
msg: foo
=====> Enter Before Aspect
=====> Enter Around Aspect
msg: bar
=====> Exit Around Aspect
=====> Enter After Aspect with result: null

This approach is nice – but has some weak points:

  • Using a factory is not as straight forward than a simple new statement.
  • Runtime reflection is used as well as a matching process on each method call (while I have to admit that I do not know how AspectJ does it after weaving the aspects in)
  • We have to define an annoying Interface (for the proxy object)
  • Traits are part of an object oriented concept, not of an aspect oriented approach AspectJ with Annotations

AspectJ defines its own syntax for Crosscutting Concerns (Aspects, Pointcuts, Advices etc.), it requires a dedicated compiler for weaving in the aspects at compile time and is based on Java. That means that Advice code has to be written in Java.

Another possibility is to to use AspectJ Annotations which is the ideal thing for using Scala Advices.

Aspects with Annotations

The example uses the following “business” object:

class LollyPop {
  def lolly(msg: String) = println("msg: " + msg)
  def pop(msg: String) = println("msg: " + msg)
}

Aspect AnnotationAspect (with the Pointcut expression execution(* *.lolly(..))) intercepts the execution of all methods lolly with a Before and Around Advice.
Rather simple, isn’t it?

@Aspect
class AnnotationAspect {
 
  @Pointcut("execution(* *.lolly(..))")
  def call2Lolly() = {}
 
  @Before("call2Lolly()")
  def beforeAdviceCall2Lolli() = println("before executing lolly")
 
  @Around("call2Lolly()")
  def aroundAdviceCall2Lolli(thisJoinPoint: ProceedingJoinPoint): Object = {
    println("Around Enter executing lolly")
    val result = thisJoinPoint.proceed
    println("Around Exit executing lolly")
    result
  }
}

AnnotationAspect2 intercepts calls to lolly with target LollyPop and catches the String parameter and the LollyPop instance itself in a Before Advice:

@Aspect
class AnnotationAspect2 {
  /**
   * more complex pointcut to get instance of LollyPop
   * and get String parameter of method lolly
   */
  @Pointcut("call(* *.lolly(String)) && args(s) && target(callee)")
  def call2LollyWithArgs(s: String, callee: LollyPop): Unit = {}
 
  @Before("call2LollyWithArgs(str, callee)")
  def beforeAdviceCall2LollyWithArgs(str: String, callee: LollyPop) = println("before call with: " + str + " to LollyPop instance: " + callee)
}

All of this works rather seamless – except Conditional Pointcuts. The Problem is that Scala does not allow to create a “public static final boolean” signature required for such kind of pointcuts.
So if you have to do this, you will need a Java class. The condition is part of the Pointcut:

public class AspectBaseForConditionalPointcut {
 
    /**
     * creating public static boolean ... method
     * required for conditional pointcuts
     */
    @Pointcut("call(* *.lolly(String)) && if() && args(s)")
    public static boolean conditionalPointcut(String s) {
        return true;
    }
}

The Advice containing class can be derived from AspectBaseForConditionalPointcut so it is possible to use Scala for the Advice, at least.

@Aspect
class ConditionalPointcut extends AspectBaseForConditionalPointcut {
 
  @After("conditionalPointcut(s)")
  def afterConditionalPointcut(s: String): Unit =
    println ("After Conditional Pointcut. Called with: " + s)
}

Using Maven

It is rather simple to use AspectJ load time weaving with maven.
Simply add the following javaagent line to the Surefire Plugin:


 maven-surefire-plugin
 
     true
     once
     
-javaagent:${user.home}/.m2/repository/org/aspectj/aspectjweaver/${aj.version}/aspectjweaver-${aj.version}.jar
     
     
                
             aj.weaving.verbose
             true
         
     
 

Some Ressources:

, , ,

  1. #1 von Jaime Metcher am 2. August 2011 - 23:02

    I agree that the factory/proxy mechanism is not satisfactory. AspectJ’s strength is precisely that it works without needing to control object creation at the source code level.

    Just to check my understanding: you advocate the use of AspectJ annotations because:
    1) it means you don’t need the ajc compiler, and
    2) therefore you can write aspects in Scala
    Correct?

  2. #2 von fakod am 15. August 2011 - 04:56

    Mainly because you can write Aspects in Scala.

  3. #3 von Nick am 2. November 2011 - 20:49

    Good introduction to using @Aspect in Scala. I was struggling over this today until I found that a for-loop in my advice method was causing a method parameter “command” to get compiled as “command$1″, so that iacj couldn’t match it up with the pointcut! Something to watch out for when weaving fails.

Kommentare geschlossen