我正在尝试使用AspectJ将调用挂接到Java API中。例如,假设我有一个java.io.File方面:

import java.io.File;

aspect FileTest {
  File around(String arg0): args(arg0) && call(public File.new(String)) {
    throw new RuntimeException("Example");
  }
}


这样就可以将调用钩接到File(String)构造函数。但是,对于以下代码,它将不执行任何操作:

public class FileLoophole extends File {
    public FileLoophole(String filename) {
        super(filename);
    }
}


根据https://eclipse.org/aspectj/doc/next/progguide/language-joinPoints.html,我应该改用execution()切入点来处理super()调用。但是,这不起作用,因为执行点位于Java API中,我无法将代码编织到其中。是否有捕获这些super()调用站点的切入点?有没有办法事先不了解FileLoophole类的方法?

最佳答案

您基本上有两个选择:


使用模式File+来匹配包括子类在内的切入点。无需知道他们的名字。
使用AspectJ二进制(编译后)编织,将方面代码直接从rt.jar注入到JDK类中,创建它的修改版本,或者仅将修改后的JDK类包装到新的JAR中,然后将其添加到引导类路径中。


尽管前一种方法是非侵入性的,并且与您在运行时环境中修改JDK的能力无关,但它也是间接的,并且并非您所要求的。后一种方法是您要的,但除了非常特殊的情况外,可能不是您想做的。

驱动程序应用程序:

package de.scrum_master.app;

import java.io.File;

public class FileLoophole extends File {
    public FileLoophole(String filename) {
        super(filename);
    }

    public static void main(String[] args) {
        new File("file.txt");
        new FileLoophole("loophole.txt");
    }
}


方面:

package de.scrum_master.aspect;

import java.io.File;

public aspect FileInterceptor {
    Object around(String fileName): call(File+.new(String)) && args(fileName) {
        System.out.println(thisJoinPoint + " -> " + fileName);
        return proceed(fileName);
    }

    void around(String fileName): execution(File+.new(String))  && args(fileName) {
        System.out.println(thisJoinPoint + " -> " + fileName);
        proceed(fileName);
    }
}


控制台输出:

call(java.io.File(String)) -> file.txt
call(de.scrum_master.app.FileLoophole(String)) -> loophole.txt
execution(de.scrum_master.app.FileLoophole(String)) -> loophole.txt


附注:请注意,尽管call(*.new(..))返回一个对象,但execution(*.new(..))却没有,这就是around()通知的返回类型为void的原因。这些语义在AspectJ documentation中描述。



更新:您在评论中询问了内部类。好吧,我的切入点适用于静态内部类,无需进行任何更改。但是,非静态内部类在其构造函数中需要其周围类的实例。检查一下,我为您创建了一个类+调试方面:

package de.scrum_master.app;

import java.io.File;

public class Application {
    private class FileLoophole extends File {
        public FileLoophole(String filename) {
            super(filename);
        }
    }

    public static void main(String[] args) {
        new File("file.txt");
        new Application().new FileLoophole("loophole.txt");
    }
}


package de.scrum_master.aspect;

public aspect FileInterceptor {
    before() : within(de.scrum_master.app.Application) {
        System.out.println(thisJoinPoint);
    }
}


现在查看控制台日志:

staticinitialization(de.scrum_master.app.Application.<clinit>)
execution(void de.scrum_master.app.Application.main(String[]))
call(java.io.File(String))
call(de.scrum_master.app.Application())
preinitialization(de.scrum_master.app.Application())
initialization(de.scrum_master.app.Application())
execution(de.scrum_master.app.Application())
call(Class java.lang.Object.getClass())
call(de.scrum_master.app.Application.FileLoophole(Application, String))
staticinitialization(de.scrum_master.app.Application.FileLoophole.<clinit>)
preinitialization(de.scrum_master.app.Application.FileLoophole(Application, String))
initialization(de.scrum_master.app.Application.FileLoophole(Application, String))
execution(de.scrum_master.app.Application.FileLoophole(Application, String))


正如您在日志末尾看到的那样,内部类的构造函数将转换为将周围的类实例作为其第一个参数的对象,从而导致不匹配。现在,知道了这一点,我们可以更改原始切入点以捕获所有构造函数:

void around(): execution(File+.new(..)) {
    System.out.println(thisJoinPoint);
    proceed();
}


如果您仍然想捕获文件名,它将变得更加复杂:

void around(String fileName): execution(File+.new(*, String)) && args(*, fileName) {
    System.out.println(thisJoinPoint + " -> " + fileName);
    proceed(fileName);
}

关于java - super()调用Java API的切入点,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31349045/

10-11 07:45