AspectJ: pointcut for lambda expressions
I have a Java 6 project that is migrating to Java 8 We use AspectJ to record some user actions, such as button clicks
So some listeners say:
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 now that we're going to use Java 8, the audience will be like this:
button.addClickListener(clickEvent -> doSth());
Is there any way to write AspectJ pointcuts so that it can handle new listeners?
Solution
I think the problem is that Lambdas does not seem to actually implement / override any interface method with the corresponding name, but creates an anonymous method Look at this example:
Virtual button class, copy the vaadin part we need:
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 classic anonymous class listener and the other with a lambda listener Click these two buttons, so the console log is as follows:
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 suggestion is to use an entry point similar to yours It can only intercept classic listener declarations
The second suggestion is for debugging purposes and to record all connection points in the driver application to show what happened here
Last but not least, the third suggestion shows a solution for intercepting lambda based listeners, which depends on the knowledge of Lambdas java compiler naming obtained from debugging output This is not very good, but at present it works Notice that buttonclick (..) The lambda version of is not public, so it's just void * Lambda *, not public void * lambda *.
Console output using 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: AspectJ now has a corresponding bugzilla issue I just created it It also points to recent discussions on mailing lists