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 theinvoke()
method is now divided intobefore()
,around()
andafter()
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
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?
Mainly because you can write Aspects in Scala.
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.