Method interception / AOP in Scala

I want to know how to best implement some cross cutting concerns when calling various methods of a class. Lets take a well known use case:

  • Log a line printing out the name of the method as well as its arguments (toString) for each and every method at the start and end of method execution. Such a use case is tremendously useful for debugging and overall observability.

I’ve come across the following approaches:

  1. Trait stacking - Achieves full separation of cross cutting concern but involves a lot of boilerplate and code duplication
  2. Pass original method to a higher order function implementing the cross cutting concern, thereby resulting in a lot of boilerplate and code duplication again
  3. AspectJ - Annotations!

Options (1 & 2) makes sense when we only need to implement cross cutting concern for a handful of methods. Even so, Option (1) becomes awkward when working with private methods. If I want to add various generic bits of logic such as logging, monitoring, tracing, etc. to each and every method then is there a way to achieve it without using AspectJ? Please suggest the recommended way of solving this problem and if there’s no idiomatic way to do it, then can we leverage any embedded scripting language for it?

AspectJ

In a small project? AspectJ. In a larger project, the power it gives would prove a liability IMO.

  1. You have also the approach that all Java frameworks take, that is runtime code generation. You can even hide it behind a macro if you wish.
  2. Define every class as a trait instead, and use a macro in a factory method to create an instance of an anonymous class overriding all methods of that interface with the advice function/object you provide to the macro.
  3. If you are still in Scala 2 land, Dynamic.
  4. In Scala 3, you might look into Selectable and having as properties objects implementing apply instead of methods, but that is a bit drastic.

Thanks a lot @Turin for your prompt response. I absolutely want to avoid AspectJ and my project is in Scala 2. Can you share some examples of (4) and (5). It seemed to me that (5) would involve a lot of repetitive code and I couldn’t understand how its different from trait stacking. Maybe (4) will work and it would be great if you can point me to a working example.

In my experience, with large projects, there’s always a lot of “other code”, which involves JAVA-code which you don’t have control over, which you’d also want to instrument. So layering any home-backed Scala fluffy-cake isn’t gonna work.

You’re absolutely right @andreak ! In fact the big motivation for a lot of this is due to a lot of Java footguns that I’ve hit. Even so, if a project’s service layer is in Scala that invokes Java code, there’s a lot of value to be had in just tracking the flow of code via metrics, logs. Metrics and traces even have business value, which is why I want to know if there’s an easy way to inject it to, say all public methods.

The only way I know how to do this efficiently, which we do, is using AspectJ. I wish the Scala community wasn’t so negative to using it…

As for bytecode instrumentation example, I can direct you only to my code:

and potentially also see how its used here:

This class reflects a method call: given a function like _.familiar.name it will create an object with a two element sequence of java.reflect.Method for Mage::familiar and Familiar::name, as well as it’s string representation: "familiar.name". It does it by first creating in the runtime a class extending Mage and ‘overriding’ all its constructors and methods in order to record called methods. It is a rather different use case and a bit more complicated, but it contains all the info you need on how to use TypeTags as well as a primer of the ByteBuddy library for bytecode instrumentation. What you want to do is:

  1. Create a class with a method of signature such as MethodInterceptor in my example, which does what you want and then calls the target method (or not) - this is the ‘Advice’.
  2. Write code which, using a bytecode instrumentation library like ByteBuddy I used, creates and caches in the runtime a subclass of any class you need to ‘advise’ - an Advisor. This will likely be a proxy wrapping the object you need advising, so you can either do it based on object.getClass, or, especially if you are concerned about type parameters, by taking an implicit TypeTag for any object you create:
    def newHamster(name :String) :Hamster = advise(new Hamster(name))
    private def advise[T :TypeTag](value :T) :T

When instructing ByteBuddy to create a subclass of a given class, you can also specify any pointcuts (i.e., advised methods) you are interested in intercepting, and give it your MethodInterceptor, which will first do the logging stuff or whatever, and then invoke the Method object it is given. In effect, you create a proxy to an (almost) arbitrary class, which extends the same class, overrides the methods you want to advise, and execute your interceptor instead (which may forward the call to the original object).

Now, this has severe limitations in the Scala world, as it requires:

  1. the proxied class to not be final
  2. the advised methodsd to not be final
  3. the class to either have a no-arg constructor, which is hard in Scala, or have a constructor for which
    parameters’ you can automatically provide acceptable values.

Point 3 is hard; you can, as I did, instrument recursively: for primitives, chose 0, for String an empty string, and recursively create dummy classes and their instances for parameters. This however has a large chance of failure if the class implements any sort of validation. I did it this way because I did not have an object to proxy, but was creating a mock from scratch having only its Class and TypeTag. What you can do relatively easily though, is also look at the parameters to the primary constructor and check if they correspond to class fields, and, if true, get the values of those fields on your proxied object T through reflection, and in your proxy class myproject.synthetic.aspects.T instrument a copycat constructor taking the same parameters and simply passing it to T - at least that’s what I would do. I hope this makes sense.

There are a lot of caveats here as you see, it is not a fool proof solution working for any class you don’t control.

1 Like

As for the macro solution, I won’t help you, because I am a noob regarding Scala 2 macros. It should be however an easy task: a Scala 2 macro is a method, any calls to which are executed at compile time, and is given as argument(s) not the values as written in code (because they don’t exist, the code isn’t running), but raw abstract syntax tree for those parameters that the compiler uses internally. The method call is then replaced in code with whatever your macro returned. It’s a super powerful feature, but also has a bit of a learning curve. So, what you would do, is never create objects you instrument directly, but create very similar factory methods:

object Hamster {
   def apply(name :String) = Macros.instrument(new MyAdvisor, new Hamster(name))
}

Executing your instrument macro would then change your code to something like:

object Hamster {
    def apply(name :String) = new Hamster(name) {
        override def goForTheEyes = 
            new MyInterceptor.intercept(classOf[Hamster].getMethod("goForTheEyes"), new Hamster(name))
    }
}

Your interceptor would do your stuff and call through reflection Method “goForTheEyes” on the real Hamster you passed as an argument. You might get away with just delegating to super on the anonymous Hamster class you created, I don’t know.

As I said, I won’t help you more as I have very little experience with macros, but while I might be incorrect in some details, I know that the above solution would work, although in order to find all methods you want to instrument, you’d have to become somewhat familiar with Scala reflection, which is hard and undocumented. Once you know however what you want to do, you can ask for more specific help here or on StackOverflow.

1 Like

However, if you want to have a fool proof solution which will work for final classes and instrument final methods, you have to go for the Dynamic + macro combo. I have never used this technique myself so again, I won’t be of much help, but basically it works like this:

  1. You create your class, such as Proxy[T](val underlying :T) as a subclass of Dynamic.
  2. On a Dynamic, you can call anything you want, passing any parameters you need, regardless of
    the fact if your proxied type T has such a method. The type safety goes out of the window, but if everything works well, then your ‘intercept’-like method in Proxy[T] is called with the method name and parameters, you call the same method on underlying.
  3. And here come macros: by making the dynamic code a macro, you can verify yourself in the compile time if the method of the specified name and signature exists in T, and report an error if it doesn’t.

I won’t tell you how to do it, but I know it’s possible and that Shapeless used to rely on it. If you google/ask for the Dynamic+macro Scala trick more informed people will know what you are talking about and help.

1 Like