【问题标题】:Aspectj: Pointcut on lambda expressionAspectj:lambda 表达式的切入点
【发布时间】:2015-06-11 09:22:31
【问题描述】:

我有一个正在迁移到 Java8 的 Java6 项目。我们使用 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 切入点,以便它处理新的侦听器?

【问题讨论】:

  • 我认为您的pointcut 应该仍然可以工作,因为您已将addClickListener 修改为使用Lambda 并且您的Button.ClickListener.buttonClick 仍然保持不变。你也修改了吗?
  • 绝对不行。我只修改了 addClickListener 以使用 lambda。是的,它仍然使用 Button.ClickListener.buttonClick 但在切入点之前执行的函数(标记为:@Before("buttonClick()")永远不会被调用。它在使用非 lambda 定义时被调用。
  • 因此这意味着 AspectJ 无法修补由 LambdaMetaFactory 在运行时生成的侦听器类的方法。但是它可以修改interfaces 的default 方法吗?这将导致一个可能的解决方案......

标签: lambda java-8 aspectj


【解决方案1】:

我想问题是 lambdas 似乎并没有真正实现/覆盖任何具有相应名称的接口方法,而是创建了一个匿名方法。看这个例子:

虚拟按钮类,在此处复制我们需要的 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();
    }
}

如您所见,创建了两个按钮实例,一个带有经典匿名类侦听器,一个带有 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);
    }
}

第一个建议使用与您类似的切入点。它只能拦截经典的监听器声明。

第二个建议用于调试目的,并记录驱动程序应用程序中的所有连接点,以显示这里到底发生了什么。

最后但并非最不重要的一点是,第三个建议显示了一种拦截基于 lambda 的侦听器的解决方法,它依赖于从调试输出中获取的关于 Java 编译器命名 lambda 的知识。这不是很好,但目前它有效。请注意,buttonClick(..) 的 lambda 版本公开,所以它只是 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$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())

更新: AspectJ 现在有一个对应的Bugzilla issue。我刚刚创建了它。它还指出了最近关于邮件列表的讨论。

【讨论】:

  • 万一其他人遇到类似的问题,一些 lambdas 也会创建静态方法,这个答案目前不跟踪。
  • 嗯,这里没有关于 lambda 中的静态方法的问题。您介意更具体并提供示例代码吗?您可以使用示例代码创建一个新问题来描述您的问题并从此处链接到它。如果可以的话,我想找你帮忙。
【解决方案2】:

对此有一个简单的解决方案。 可以编写切入点拦截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/

【讨论】:

    猜你喜欢
    • 2015-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 2012-12-31
    • 1970-01-01
    相关资源
    最近更新 更多