重新定义从另一个编译方法调用的方法的方法签名

时间:2014-03-20 22:58:55

标签: java javassist

我正在尝试使用具有不同返回类型的不同方法替换方法,但似乎我能够成功执行此操作的唯一方法是使所有方法的源代码具有对替换方法的调用站点。我甚至尝试使用调用方法来调用新的替换方法,但是我得到了一个VerifyError(操作数堆栈上的错误类型)。在重建依赖方法时,有没有办法只使用字节码而不依赖源代码?

功能示例(具有源代码依赖性)

public class OverrideTest {  
    final ClassPool POOL = ClassPool.getDefault();  

    class Foo {  
        public Integer getFoo() { return new Integer(1); }  
        public String doA() { return getFoo().toString(); }  
    }  

    class FooReplacement {  
        public String getFoo() { return "A"; }  
    }  

    @Test  
    public void d() throws Throwable {  
        CtClass srcClass = POOL.getCtClass(Foo.class.getName());  
        CtClass extClass = POOL.getCtClass(FooReplacement.class.getName());  
        CtClass d = POOL.makeClass("Derp");  
        CtClass STRING = POOL.get("java.lang.String");  
        CtClass INT = POOL.get("java.lang.Integer");  
        {  
            CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));  
            CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));  
            CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);  
            CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);  
            d.addMethod(doA);  
            d.addMethod(getFoo);  
            doA.setBody(doA1, null);  
            getFoo.setBody(getFoo1, null);  
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);  

            d.removeMethod(getFoo);  
            CtMethod getFooReplaced = new CtMethod(STRING, "getFoo", new CtClass[0], d);  
            d.addMethod(getFooReplaced);  
            CtMethod getFooReplaced1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));  
            getFooReplaced.setBody(getFooReplaced1, null);  

            doA.setBody("{ return getFoo().toString(); }");  
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);  
        }  
        {  
            Class<?> c = d.toClass();  
            Constructor<?> ctor = c.getConstructor();  
            Object derp = ctor.newInstance();  
            Method getFoo = derp.getClass().getMethod("getFoo");  
            Method doA = derp.getClass().getMethod("doA");  
            Object doResult = doA.invoke(derp);  
            Object getResult = getFoo.invoke(derp);  
            assertEquals("A", getResult);  
            assertEquals("A", doResult);  
        }  
    }  
}  

非功能性示例(VerifyError)

public class OverrideTest {  
    final ClassPool POOL = ClassPool.getDefault();  


    class Foo {  
        public Integer getFoo() { return new Integer(1); }  
        public String doA() { return getFoo().toString(); }  
    }  


    class FooReplacement {  
        public String getFoo() { return "A"; }  
    }  


    @Test  
    public void d() throws Throwable {  
        CtClass srcClass = POOL.getCtClass(Foo.class.getName());  
        CtClass extClass = POOL.getCtClass(FooReplacement.class.getName());  
        CtClass d = POOL.makeClass("Derp");  
        CtClass STRING = POOL.get("java.lang.String");  
        CtClass INT = POOL.get("java.lang.Integer");  
        {  
            CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));  
            CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));  
            CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);  
            CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);  
            d.addMethod(doA);  
            d.addMethod(getFoo);  
            doA.setBody(doA1, null);  
            getFoo.setBody(getFoo1, null);  
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);  


            CtMethod tempMethod = new CtMethod(getFoo.getReturnType(), "tempFoo", new CtClass[0], d);  
            d.addMethod(tempMethod);  
            doA.instrument(new MethodReplacer(getFoo, tempMethod));  
            d.removeMethod(getFoo);  


            CtMethod getFooReplaced = new CtMethod(STRING, "getFoo", new CtClass[0], d);  
            d.addMethod(getFooReplaced);  
            CtMethod getFooReplaced1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));  
            getFooReplaced.setBody(getFooReplaced1, null);  


            doA.instrument(new MethodReplacer(tempMethod, getFooReplaced));  
            d.removeMethod(tempMethod);  
            d.removeMethod(doA);  
            CtMethod doA2 = new CtMethod(STRING, "doA", new CtClass[0], d);  
            d.addMethod(doA2);  
            doA2.setBody(doA, null);  
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);  
        }  
        {  
            Class<?> c = d.toClass();  
            Constructor<?> ctor = c.getConstructor();  
            Object derp = ctor.newInstance();  
            Method getFoo = derp.getClass().getMethod("getFoo");  
            Method doA = derp.getClass().getMethod("doA");  
            Object doResult = doA.invoke(derp);  
            Object getResult = getFoo.invoke(derp);  
            assertEquals("A", getResult);  
            assertEquals("A", doResult);  
        }  
    }  


    class MethodReplacer extends ExprEditor {  
        private CtMethod replacedMethod;  
        private CtMethod replacement;  


        MethodReplacer(CtMethod replacedMethod, CtMethod replacement) {  
            this.replacedMethod = replacedMethod;  
            this.replacement = replacement;  
        }  


        @Override  
        public void edit(MethodCall mcall) throws CannotCompileException {  
            CtClass declaringClass = replacedMethod.getDeclaringClass();  
            try {  
                CtMethod m = mcall.getMethod();  
                if (declaringClass.equals(m.getDeclaringClass()) && m.equals(replacedMethod))  
                    mcall.replace("$_ = " + replacement.getName()+"($$);");  
            } catch (NotFoundException e) {  
                throw new RuntimeException("Unable to instrument a dependent method call to " + replacedMethod.getName(), e);  
            }  
        }  


    }  
}  

1 个答案:

答案 0 :(得分:1)

使用单个方法编译类时:

class Foo {

  Object bar() { }
}

此方法的描述符是方法签名的一部分,在编译后看起来像这样:

()Ljava/lang/Object;

请注意,返回类型是方法签名的一部分!因此,对方法的任何调用都是显式的,返回类型为Object的实例的方法。如果将返回类型更改为例如Integer,则描述符将改为()Ljava/lang/Integer;

如果您编译了一个类Qux以使用Foo描述符调用()Ljava/lang/Object;的方法,但随后更改了Foo中的方法以返回Integer相反,JVM无法在不重新编译Qux的情况下调度方法调用:对于JVM,调用的方法Qux不再存在。当您使用像javassist这样的工具仅“重新编译”单个方法而不重新编译调用第一个方法的方法时,同样的问题适用于Foo中定义的另一个方法。这是验证者在你的例子中抱怨的问题。

但是,Java编译器为此目的知道桥接方法的概念,其中Foo在表示生成的Java字节代码的伪Java代码中看起来像。

class Foo {

  Object bar() { this.[Integer]bar(); }

  Integer bar() { }
}

编译后,this.[Integer]bar();表示对bar方法的调用,该方法返回Integer[Object]bar()方法表示此上下文中的桥接方法。您可以通过首先重新定义方法签名以返回不同类型并另外添加具有原始返回类型的桥接方法来自己模拟这种桥接方法的创建。