Измените метод, объявленный в суперклассе, с помощью Javassist

Я пытаюсь изменить метод с CtMethod#insertBefore, который объявлен в суперклассе. Однако с 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, но если я вызываю метод getConnection(), объявленный в AbstractTestDataSource, ничего не происходит. Что я делаю не так?

Изменить

Я могу подтвердить, что оба метода инструментированы, потому что это то, что выводится на консоль:

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

person Felix    schedule 06.04.2015    source источник
comment
Вы определили, что метод insertBefore вызывается дважды, но не проверил, для чего он на самом деле вызывается. Попробуйте напечатать и имя метода, и дескриптор. Еще лучше, посмотрите, сможете ли вы получить фактический файл класса для целевого класса и дизассемблировать его.   -  person Antimony    schedule 06.04.2015
comment
Хорошо, кажется, я знаю, в чем проблема. Суперкласс не изменяется, потому что в методе преобразования я могу преобразовать только один загруженный в данный момент класс. Я думаю, что теперь у меня есть варианты либо повторно преобразовать суперкласс, либо динамически перезаписать метод суперкласса. Сейчас пробую последний вариант...   -  person Felix    schedule 06.04.2015


Ответы (2)


Мое решение — проверить, объявлен ли метод 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;
}

Это не похоже на идеальное решение, но оно работает нормально.

person Felix    schedule 06.04.2015
comment
Я до сих пор не понимаю, как ваш исходный код не работал. Согласно документации по Java. , вы изменяли метод суперкласса. Обратите внимание, что если метод унаследован от суперкласса, то тот же объект CtMethod, который представляет унаследованный метод, представляет метод, объявленный в этом суперклассе. Объект CtMethod соответствует каждому объявлению метода. - person Anssssss; 06.04.2015
comment
Может быть, тот, кто отвечает за проект, знает об этом (может быть, это ошибка). Я вижу эту страницу Jira для этого: issues.jboss.org/browse/JASSIST - person Anssssss; 06.04.2015
comment
Проблема в том, что я использую javassist внутри -javaagent для изменения классов во время загрузки класса. Javaagent можно рассматривать как фильтр, который выполняется всякий раз, когда загружается класс. Соответствующий метод — public byte[] transform(..) в ClassFileTransformer. Внутри этого метода вы можете изменить byte[] текущего загруженного класса (и только этого класса), вернув измененный byte[]. Поэтому никакие модификации других классов (даже суперкласса) не применяются. - person Felix; 06.04.2015

Вы не переопределяете метод getConnection() в суперклассе, этот метод имеет эту сигнатуру метода:

public Connection getConnection() throws SQLException

В вашем дочернем классе вам придется переопределить этот метод с той же сигнатурой метода, иначе вы просто создадите новый метод в дочернем классе.

person Arlind    schedule 06.04.2015
comment
Я думаю, вы меня неправильно поняли. Я хочу изменить как getConnection(), так и getConnection(String, String), однако кажется возможным изменить только методы, объявленные в дочернем классе, а не в суперклассе. - person Felix; 06.04.2015