2013-08-10 1 views
3

Хорошо, я пытаюсь сделать java-агент, который будет контролировать приложение. Итак, я пытаюсь ввести код в PreparedStatements для измерения времени выполнения запросов SQL. Для этого я разработал класс, реализующий ClassFileTransformer. Похоже, что:ClassFileTransformer + Javassist: no такое поле

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();"); 
    } 

} 

Итак, что я хочу сделать, это инъекционные код в каждом классе (в «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, и исключение также выбрасывается. Оба ServerPreparedStatement и JDBC4PreparedStatement продлить com.mysql.jdbc.PreparedStatement так может быть, это как-то связаны ...

Я новичок в Javassist и Java агент вещи, так что, может быть, ответ очевиден, но я не могу получить, почему это исключение.

ответ

3

Я думаю, что проблема заключается в следующем:
Вы добавляете поле $$probe в currentClass (ваш 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 первого суперкласса, который реализует (или @Override) executeQuery() и вводят $$probe в или @OverrideexecuteQuery() в текущем классе

Foe пример, переопределение 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;} 

    @Override 
    public java.sql.ResultSet executeQuery() { 
    this.$$probe.start(); 
    super.executeQuery(); 
    this.$$probe.end(); 
    } 
} 

Я надеюсь, что это будет ясно, английский не мой родной язык.

+0

Спасибо, ты положил меня на правильный путь! Я на самом деле исправил его, используя [getDeclaredMethod] (http://www.csg.ci.iu-tokyo.ac.jp/~chiba/javassist/html/javassist/CtClass.html#getDeclaredMethod%28java.lang.String,% 20javassist.CtClass []% 29). getDeclaredMethod выдает исключение NotFoundException, когда метод не объявлен в текущем классе. Неправильно использовать блок try/catch, но я не смог найти способ проверить, объявлен ли метод в классе. – mael

+0

Как только проблема найдена, решение находится под углом. Надеюсь, вы скоро найдете хорошее решение! –

Смежные вопросы