ClassFileTransformer + Javassist:没有这样的字段

时间:2013-08-10 21:02:04

标签: java javassist javaagents

好的,我要做的是做一个监视应用程序的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();");
    }

}

所以,我想要做的是在每个类中(在#34; executeQuery()"方法中)注入实现java.sql.PreparedStatement的代码。 为了测试它,我只是运行一个基本的" select * from a_table"在MySQL数据库上,代理程序连接到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调用该方法,并且也抛出异常。 ServerPreparedStatementJDBC4PreparedStatement都延伸com.mysql.jdbc.PreparedStatement,所以也许它会以某种方式相关......

我是javassist和java代理的新手,所以也许答案很明显,但我无法理解抛出此异常的原因。

1 个答案:

答案 0 :(得分:3)

我认为问题是这样的:
您在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,这可能会导致编译错误异常(当你检测{时{1}}没有抛出任何错误,因为您将字段注入com.mysql.jdbc.PreparedStatement实现者​​类。) 你遇到这种情况(只是一个想法):

java.sql.PreparedStatement.executeQuery()

从currentClass中提取实现(或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(); } } @Override的第一个超类并将executeQuery()注入当前类中的$$probe @Override

敌人的例子,在currentClass中覆盖executeQuery(),你将拥有

executeQuery()

我希望明白,英语不是我的母语。