【问题标题】:ClassFileTransformer + Javassist: no such fieldClassFileTransformer + Javassist:没有这样的字段
【发布时间】:2013-08-10 21:02:03
【问题描述】:

好的,我正在尝试做一个 Java 代理来监控应用程序。所以,我试图在 PreparedStatements 中注入代码来测量 SQL 查询的执行时间。为此,我开发了一个实现 ClassFileTransformer 的类。看起来是这样的:

public class JDBCClassTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        ClassPool pool = ClassPool.getDefault();
        CtClass currentClass = null;
        CtClass statement = null;
        try {
            currentClass = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
            statement = pool.get("java.sql.PreparedStatement");
            if (currentClass.subtypeOf(statement) && !currentClass.isInterface()) {
                probeStatement(currentClass);
            }
            classfileBuffer = currentClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            if (currentClass != null) {
                currentClass.detach();
            }
        }
        return classfileBuffer;

    }

    private void probeStatement(CtClass currentClass) throws NotFoundException, CannotCompileException {
        CtField field = CtField.make("private fr.mael.package.SQLProbe $$probe;", currentClass);
        currentClass.addField(field);
        CtMethod setter = CtNewMethod.make("public void set$$probe(fr.mael.package.SQLProbe probe){ this.$$probe = probe;}", currentClass);
        currentClass.addMethod(setter);

        CtMethod executeQuery = currentClass.getMethod("executeQuery", "()Ljava/sql/ResultSet;");

        executeQuery.insertBefore("this.$$probe.start();");
        executeQuery.insertAfter("this.$$probe.stop();");
    }

}

所以,我想做的是在每个实现 java.sql.PreparedStatement 的类中(在“executeQuery()”方法中)注入代码。 为了测试它,我只是在 MySQL 数据库上运行一个基本的“select * from a_table”,代理连接到 JVM。但我收到以下异常:

javassist.CannotCompileException: [source error] no such field: $$probe
    at javassist.CtBehavior.insertBefore(CtBehavior.java:725)
    at javassist.CtBehavior.insertBefore(CtBehavior.java:685)

它发生在executeQuery.insertBefore("this.$$probe.start();"); 线上。 奇怪的是,每次执行“probeStatement”方法时都不会发生这种情况:首先为类com.mysql.jdbc.PreparedStatement调用该方法,并且没有抛出异常。然后,为类com.mysql.jdbc.ServerPreparedStatement 调用该方法。抛出异常。类com.mysql.jdbc.JDBC4PreparedStatement 也调用了该方法,并且也抛出了异常。 ServerPreparedStatementJDBC4PreparedStatementextend com.mysql.jdbc.PreparedStatement 所以也许它在某种程度上是相关的......

我是 javassist 和 java 代理的新手,所以答案可能很明显,但我不明白为什么会抛出这个异常。

【问题讨论】:

    标签: java javassist javaagents


    【解决方案1】:

    我认为问题是这样的:
    您在 currentClass 中添加字段 $$probe(您的 public class com.mysql.jdbc.ServerPreparedStatement extends com.mysql.jdbc.PreparedStatement,不直接实现 java.sql.PreparedStatement)但您是覆盖方法 com.mysql.jdbc.PreparedStatement.executeQuery()来自 javadoc:CtClass.getMethod() -返回的方法可能在超类中声明。)并且您没有将字段注入com.mysql.jdbc.PreparedStatement,这可能会导致编译错误异常(当您检测com.mysql.jdbc.PreparedStatement时不会抛出错误,因为您是注入字段到java.sql.PreparedStatement.executeQuery() 实现类)。
    您遇到这种情况(只是一个想法):

    class com.mysql.jdbc.ServerPreparedStatement$$Probed {
      private fr.mael.package.SQLProbe $$probe;
    
      public void set$$probe(fr.mael.package.SQLProbe probe){ this.$$probe = probe;}
    }
    class com.mysql.jdbc.PreparedStatement$$Probed {
      public java.sql.ResultSet executeQuery() {
        // this.$$probe in this class doesn't exists! 
        this.$$probe.start();
        ...
        this.$$probe.end();
      }
    }
    

    从 currentClass 中提取第一个实现(或 @OverrideexecuteQuery() 的超类,并将 $$probe 注入当前类中的或 @Override executeQuery()

    例如,在你将拥有的 currentClass 中覆盖 executeQuery()

    class com.mysql.jdbc.ServerPreparedStatement$$Probed {
      private fr.mael.package.SQLProbe $$probe;
    
      public void set$$probe(fr.mael.package.SQLProbe probe){ this.$$probe = probe;}
    
      @Override
      public java.sql.ResultSet executeQuery() {
        this.$$probe.start();
        super.executeQuery();
        this.$$probe.end();
      }
    }
    

    我希望清楚,英语不是我的母语。

    【讨论】:

    • 谢谢你,你让我走上了正轨!我实际上使用getDeclaredMethod 修复了它。当方法未在当前类中声明时,getDeclaredMethod 会引发 NotFoundException。使用 try / catch 块感觉不对,但我找不到测试方法是否在类中声明的方法。
    • 一旦发现问题,解决方案就在眼前。希望您能尽快找到好的解决方案!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-18
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多