我有一个问题,我无法解决。假设我们有以下两个类和一个继承关系:
public class A {
}
public class B extends A {
public void foo() {}
}
我想要检测其他代码,使其看起来如下:
public class A {
public void print() { }
}
public class B extends A {
public void foo() { print(); }
}
为了实现这一目标,我将我的实现基于java.lang.instrument
包,使用具有我自己的类文件转换器的代理。该机制也称为动态字节码检测。
到目前为止一块蛋糕。 现在,我的测试方法执行以下操作:
代码:
B b = new B();
b.foo();
由于instrumentation包中的以下限制,这不起作用:当调用new B()
时,检测从B类开始,并在加载被操作的类时最终出现编译错误,因为超类A没有print()方法呢!问题是我是否以及如何在类B之前触发类A的检测。我的classfiletransformer的transform()方法应该显式地用类A调用!所以我开始阅读并碰到了这个:
java.lang.instrument.ClassFileTransformer.transform()
的javadoc说:
将要求变压器 每个新的类定义和每个 类重新定义。请求一个 新的类定义是用 ClassLoader.defineClass。请求 用于重新定义类 Instrumentation.redefineClasses或其 原生等价物。
转换方法附带了一个类加载器实例,所以我想,为什么不在B的仪器上用类A调用loadClass
方法(loadClass
调用defineClass
)已经开始。
我预计仪器方法将被调用,但遗憾的是情况并非如此。而是在没有检测的情况下加载了类A
。 (代理不会拦截加载过程,尽管应该这样做)
任何想法,如何解决这个问题?您是否看到为什么操作某些字节码的代理无法手动加载另一个类,然后希望通过该/任何代理发送的类?
请注意,以下代码正常工作,因为A在加载B之前已加载并进行了检测。
A a = new A();
B b = new B();
b.foo();
非常感谢!
答案 0 :(得分:8)
当我在Sun 1.6.0_15和1.5.0_17 JRE(我使用ASM)上的A之前转换B时,我没有看到任何问题。我会通过在外部运行并检查结果类(例如使用javap)来仔细检查转换代码。我还会检查你的类路径配置,以确保在你的代理之前没有加载A由于某种原因(也许用getAllLoadedClasses检查你的premain。)
编辑:
如果您在代理中加载类A
,请执行以下操作:
Class.forName("A");
...然后抛出异常:
Exception in thread "main" java.lang.NoSuchMethodError: B.print()V
这是有道理的 - A
成为代理的依赖关系,代理程序检测自己的代码是没有意义的。你会得到一个无限循环,导致堆栈溢出。因此,A
不会处理ClassFileTransformer
。
为了完整性,这是我的测试代码,没有问题。如上所述,它取决于ASM库。
代理人:
public class ClassModifierAgent implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("transform: " + className);
if ("A".equals(className)) {
return new AModifier().modify(classfileBuffer);
}
if ("B".equals(className)) {
return new BModifier().modify(classfileBuffer);
}
return classfileBuffer;
}
/** Agent "main" equivalent */
public static void premain(String agentArguments,
Instrumentation instrumentation) {
instrumentation.addTransformer(new ClassModifierAgent());
}
}
A
的方法注入器:
public class AModifier extends Modifier {
@Override
protected ClassVisitor createVisitor(ClassVisitor cv) {
return new AVisitor(cv);
}
private static class AVisitor extends ClassAdapter {
public AVisitor(ClassVisitor cv) { super(cv); }
@Override
public void visitEnd() {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "print", "()V",
null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn("X");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
super.visitEnd();
}
}
}
B
的方法替换器:
public class BModifier extends Modifier {
@Override
protected ClassVisitor createVisitor(ClassVisitor cv) {
return new BVisitor(cv);
}
class BVisitor extends ClassAdapter {
public BVisitor(ClassVisitor cv) { super(cv); }
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if ("foo".equals(name)) {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "foo", "()V",
null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "B", "print", "()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
return new EmptyVisitor();
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
}
}
通用基本代码:
public abstract class Modifier {
protected abstract ClassVisitor createVisitor(ClassVisitor cv);
public byte[] modify(byte[] data) {
ClassReader reader = new ClassReader(data);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
ClassVisitor visitor = writer;
visitor = new CheckClassAdapter(visitor);
visitor = createVisitor(visitor);
reader.accept(visitor, 0);
return writer.toByteArray();
}
}
对于一些可见的结果,我向System.out.println('X');
添加了A.print()
。
在此代码上运行时:
public class MainInstrumented {
public static void main(String[] args) {
new B().foo();
}
}
...它产生了这个输出:
transform: MainInstrumented
transform: B
transform: A
X