Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aspectj: Pointcut on lambda expression

I have a Java6 project that is being migrated to Java8. We used aspectj to log some of users actions, like button clicking.

So there are listeners like this:

    button.addClickListener(new Button.ClickListener() {
        @Override
        public void buttonClick(Button.ClickEvent clickEvent) {
            doSth();
        }
    });

And poincut:

@Pointcut("execution(public void Button.ClickListener.buttonClick(Button.ClickEvent))")
public void buttonClick() {};

But since we will use Java8, listeners will be like this:

button.addClickListener(clickEvent -> doSth());

Is there any way to write aspectj pointcut, so that it handles new listeners?

like image 445
Lete Avatar asked Jun 11 '15 09:06

Lete


1 Answers

I guess the problem is that lambdas do not seem to actually implement/override any interface methods with a corresponding name, but create an anonymous method. Look at this example:

Dummy button class, replicating the parts of Vaadin we need here:

package de.scrum_master.app;

public class Button {
    private ClickListener listener;

    public void addClickListener(ClickListener listener) {
        this.listener = listener;
    }

    public void click() {
        System.out.println("Clicking button");
        listener.buttonClick(new ClickEvent());
    }

    public static class ClickEvent {}

    public static interface ClickListener {
        public void buttonClick(ClickEvent clickEvent);
    }
}

Driver application:

package de.scrum_master.app;

public class Application {
    protected static void doSomething() {}

    public static void main(String[] args) {
        Button button = new Button();
        button.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent clickEvent) {
                doSomething();
            }
        });
        button.click();

        button = new Button();
        button.addClickListener(clickEvent -> doSomething());
        button.click();
    }
}

As you can see, two button instances are created, one with a classical anonymous class listener, one with a lambda listener. Both buttons get clicked, so consequently the console log looks like this:

Clicking button
Clicking button

Aspect:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class ButtonClickLogger {
    @Before("execution(public void *..Button.ClickListener.buttonClick(*..Button.ClickEvent))")
    public void logButtonClick(JoinPoint thisJoinPoint) {
        System.out.println("Caught button click: " + thisJoinPoint);
    }

    @Before("within(*..Application)")
    public void logAllInListener(JoinPoint thisJoinPoint) {
        System.out.println("  " + thisJoinPoint);
    }

    @Before("execution(void *..lambda*(*..Button.ClickEvent))")
    public void logButtonClickLambda(JoinPoint thisJoinPoint) {
        System.out.println("Caught button click (lambda): " + thisJoinPoint);
    }
}

The first advice uses a pointcut similar to yours. It can only intercept classical listener declarations.

The second advice is for debugging purposes and logs all joinpoints within the driver application in order to show what the heck is going on here.

Last, but not least, the third advice shows a workaround for intercepting lambda-based listeners, relying on the knowledge about Java compiler naming for lambdas acquired from the debug output. This is not very nice, but for the moment it works. Please note that the lambda version of buttonClick(..) is not public, so it is just void *..lambda*, not public void *..lambda*.

Console output with AspectJ:

  staticinitialization(de.scrum_master.app.Application.<clinit>)
  execution(void de.scrum_master.app.Application.main(String[]))
  call(de.scrum_master.app.Button())
  call(de.scrum_master.app.Application.1())
  staticinitialization(de.scrum_master.app.Application.1.<clinit>)
  preinitialization(de.scrum_master.app.Application.1())
  initialization(de.scrum_master.app.Application.1())
  initialization(de.scrum_master.app.Button.ClickListener())
  execution(de.scrum_master.app.Application.1())
  call(void de.scrum_master.app.Button.addClickListener(Button.ClickListener))
  call(void de.scrum_master.app.Button.click())
Clicking button
Caught button click: execution(void de.scrum_master.app.Application.1.buttonClick(Button.ClickEvent))
  execution(void de.scrum_master.app.Application.1.buttonClick(Button.ClickEvent))
  call(void de.scrum_master.app.Application.doSomething())
  execution(void de.scrum_master.app.Application.doSomething())
  call(de.scrum_master.app.Button())
  call(void de.scrum_master.app.Button.addClickListener(Button.ClickListener))
  call(void de.scrum_master.app.Button.click())
Clicking button
  execution(void de.scrum_master.app.Application.lambda$0(Button.ClickEvent))
Caught button click (lambda): execution(void de.scrum_master.app.Application.lambda$0(Button.ClickEvent))
  call(void de.scrum_master.app.Application.doSomething())
  execution(void de.scrum_master.app.Application.doSomething())

Update: There is a corresponding Bugzilla issue now for AspectJ. I have just created it. It also points to a recent discussion on the mailing list.

like image 139
kriegaex Avatar answered Sep 28 '22 17:09

kriegaex