2015-04-06 47 views
1

我试图修改在超类中声明的方法CtMethod#insertBefore。但是,Javassist似乎不可能。修改Javassist在超类中声明的方法

private class AbstractTestDataSource { 
    public Connection getConnection() throws SQLException { 
     return connection; 
    } 
} 

private class TestDataSource extends AbstractTestDataSource implements DataSource { 
    public Connection getConnection(String username, String password) throws SQLException { 
     return connection; 
    } 
    // other methods omitted 
} 

这是我ClassFileTransformer

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
            ProtectionDomain protectionDomain, byte[] classfileBuffer) 
     throws Exception { 
    if (!className.equals("org/example/TestDataSource")) { 
     return classfileBuffer; 
    } 
    final CtClass ctClass = createCtClass(loader, classfileBuffer); 
    for (CtMethod method : ctClass.getMethods()) { 
     if (method.getName().equals("getConnection")) { 
      System.out.print(method.getName()); 
      System.out.println(method.getSignature()); 
      method.insertBefore("System.out.println(\"foo\");"); 
     } 
    } 
    return ctClass.toBytecode(); 
} 

当我打电话getConnection(String, String)方法,foo被打印到控制台,但如果我认为这种声明在AbstractTestDataSource没有任何反应getConnection()方法。 我在做什么错?

编辑

我可以证实,这两种方法都仪器,因为这是被打印到控制台:

getConnection(Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection; 
getConnection()Ljava/sql/Connection; 
+0

你已经确定insertBefore被调用了两次,但你没有检查它实际被调用的是什么。尝试打印方法名称和描述符。更好的是,看看你是否可以获得目标类的实际类文件并反汇编它。 – Antimony 2015-04-06 14:34:59

+0

好吧,我想我知道问题在哪里。超类没有被修改,因为在transform方法中,我只能转换当前加载的一个类。我想我现在的选择是重新转换超类或动态覆盖超类方法。现在尝试后一种选择... – Felix 2015-04-06 14:54:54

回答

1

我的解决方案是检查getConnection方法是否在除当前类之外的类中声明。

if (!ctClass.equals(method.getDeclaringClass())) { 
    method = overrideMethod(ctClass, method); 
} 

如果是这样,创建(因而覆盖)getConnection方法和代表对超类。

private CtMethod overrideMethod(CtClass ctClass, CtMethod getConnectionMethodOfSuperclass) 
     throws NotFoundException, CannotCompileException { 
    final CtMethod m = CtNewMethod.delegator(getConnectionMethodOfSuperclass, ctClass); 
    ctClass.addMethod(m); 
    return m; 
} 

它不觉得是理想的解决方案,但它工作正常。

+0

我仍然没有看到你的原代码是不是工作。根据[Javassist文档](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/tutorial/tutorial2.html),您正在修改超类的方法。 “请注意,如果某个方法是从超类继承的,则表示继承方法的同一CtMethod对象表示在该超类中声明的方法,CtMethod对象对应于每个方法声明。 – Anssssss 2015-04-06 18:05:07

+0

也许任何负责该项目的人都能洞察它(也许这是一个错误)。我看到它的这个Jira页面:https://issues.jboss.org/browse/JASSIST – Anssssss 2015-04-06 18:15:33

+1

问题是,我使用'-javaagent'内的javassist来在类加载时修改类。一个javaagent可以被看作是一个过滤器,每当一个类被加载时执行。相关的方法是'ClassFileTransformer'中的'public byte [] transform(..)'。在该方法中,您可以通过返回一个修改过的byte []来修改当前加载的类(只有该类)的'byte []'。这就是为什么对其他类(即使是超类)的任何修改都不适用。 – Felix 2015-04-06 18:43:14

0

你是不是在超类中重写方法的getConnection()该方法有此方法签名:

public Connection getConnection() throws SQLException 

在您的子类中,您必须重写此方法使用相同的方法签名,否则你只是在子类中创建一个新方法

+0

我想你误解了我。我想修改'getConnection()'和'getConnection(String,String)',但它似乎只能修改在子类中声明的方法而不是超类。 – Felix 2015-04-06 11:27:01