Aspectj:lambda 表达式的切入点
Aspectj: Pointcut on lambda expression
我有一个 Java6 项目正在迁移到 Java8。我们使用 aspectj 来记录一些用户操作,比如按钮点击。
所以有这样的听众:
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent clickEvent) {
doSth();
}
});
和切入点:
@Pointcut("execution(public void Button.ClickListener.buttonClick(Button.ClickEvent))")
public void buttonClick() {};
但由于我们将使用 Java8,听众将是这样的:
button.addClickListener(clickEvent -> doSth());
有什么方法可以编写 aspectj 切入点,以便处理新的侦听器吗?
我想问题是 lambda 似乎实际上 implement/override 任何具有相应名称的接口方法,而是创建一个匿名方法。看这个例子:
虚拟按钮class,在此处复制我们需要的 Vaadin 部分:
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);
}
}
驱动申请:
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();
}
}
如您所见,创建了两个按钮实例,一个带有 classical 匿名 class 侦听器,一个带有 lambda 侦听器。两个按钮都被点击,因此控制台日志如下所示:
Clicking button
Clicking button
看点:
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);
}
}
第一个建议使用类似于您的切入点。它只能拦截 classical 侦听器声明。
第二个建议是为了调试目的,记录驱动程序应用程序中的所有连接点,以显示这里到底发生了什么。
最后但同样重要的是,第三条建议显示了拦截基于 lambda 的侦听器的解决方法,依赖于从调试输出中获取的有关 Java lambda 编译器命名的知识。这不是很好,但目前它有效。请注意 buttonClick(..)
的 lambda 版本是 而不是 public,所以它只是 void *..lambda*
,而不是 public void *..lambda*
.
使用 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[=14=](Button.ClickEvent))
Caught button click (lambda): execution(void de.scrum_master.app.Application.lambda[=14=](Button.ClickEvent))
call(void de.scrum_master.app.Application.doSomething())
execution(void de.scrum_master.app.Application.doSomething())
更新: AspectJ 现在有相应的 Bugzilla issue。我刚刚创建了它。它还指向最近在邮件列表上的讨论。
对此有一个简单的解决方案。 可以写切入点拦截lambda表达式的点击事件
@Pointcut("execution(void *..lambda*(android.view.View))")
public void dealWithLambda() {
}
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
@Before("( dealWithNormal() || dealWithLambda()) && args(view)")
public void beforeClick(View view) {
Log.e(TAG, "before: " + ((TextView) view).getText().toString();
}
目前我使用 "view" 作为 beforeClick(View view) 的参数,但您可以根据需要将 JoinPoint 或 ProceedingJoinPoint 作为参数。
更多参考请参考以下文章:
https://www.programmersought.com/article/38871580011/
我有一个 Java6 项目正在迁移到 Java8。我们使用 aspectj 来记录一些用户操作,比如按钮点击。
所以有这样的听众:
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent clickEvent) {
doSth();
}
});
和切入点:
@Pointcut("execution(public void Button.ClickListener.buttonClick(Button.ClickEvent))")
public void buttonClick() {};
但由于我们将使用 Java8,听众将是这样的:
button.addClickListener(clickEvent -> doSth());
有什么方法可以编写 aspectj 切入点,以便处理新的侦听器吗?
我想问题是 lambda 似乎实际上 implement/override 任何具有相应名称的接口方法,而是创建一个匿名方法。看这个例子:
虚拟按钮class,在此处复制我们需要的 Vaadin 部分:
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);
}
}
驱动申请:
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();
}
}
如您所见,创建了两个按钮实例,一个带有 classical 匿名 class 侦听器,一个带有 lambda 侦听器。两个按钮都被点击,因此控制台日志如下所示:
Clicking button
Clicking button
看点:
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);
}
}
第一个建议使用类似于您的切入点。它只能拦截 classical 侦听器声明。
第二个建议是为了调试目的,记录驱动程序应用程序中的所有连接点,以显示这里到底发生了什么。
最后但同样重要的是,第三条建议显示了拦截基于 lambda 的侦听器的解决方法,依赖于从调试输出中获取的有关 Java lambda 编译器命名的知识。这不是很好,但目前它有效。请注意 buttonClick(..)
的 lambda 版本是 而不是 public,所以它只是 void *..lambda*
,而不是 public void *..lambda*
.
使用 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[=14=](Button.ClickEvent))
Caught button click (lambda): execution(void de.scrum_master.app.Application.lambda[=14=](Button.ClickEvent))
call(void de.scrum_master.app.Application.doSomething())
execution(void de.scrum_master.app.Application.doSomething())
更新: AspectJ 现在有相应的 Bugzilla issue。我刚刚创建了它。它还指向最近在邮件列表上的讨论。
对此有一个简单的解决方案。 可以写切入点拦截lambda表达式的点击事件
@Pointcut("execution(void *..lambda*(android.view.View))")
public void dealWithLambda() {
}
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
@Before("( dealWithNormal() || dealWithLambda()) && args(view)")
public void beforeClick(View view) {
Log.e(TAG, "before: " + ((TextView) view).getText().toString();
}
目前我使用 "view" 作为 beforeClick(View view) 的参数,但您可以根据需要将 JoinPoint 或 ProceedingJoinPoint 作为参数。
更多参考请参考以下文章: https://www.programmersought.com/article/38871580011/