用于对Java API的super()调用的切入点

时间:2015-07-10 19:43:46

标签: java aop aspectj

我正在尝试使用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()callite?有没有办法在不事先了解FileLoophole类的情况下完成它?

1 个答案:

答案 0 :(得分:1)

您基本上有两个选择:

  • 使用模式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

P.S。:请注意,虽然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);
}