Как инструментировать классы, загружаемые пользовательским загрузчиком классов?

Я пытался изменить байт-код нескольких классов, чьи упаковочные файлы jar не находятся в пути к классу - они загружаются с помощью пользовательского ClassLoader во время выполнения с учетом URL-адреса. Я пытался использовать java agent с ClassFileTransformer в надежде перехватить эти классы, но потерпел неудачу. Загрузчик классов является частью устаревшего проекта, поэтому я не могу вносить в него изменения напрямую.

Агент отлично работает с классами, загружаемыми AppClassLoader «локально», но просто игнорирует классы, загруженные пользовательским загрузчиком классов.

CustomClassLoader:

    public class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader(URL[] urls) {
        super(urls, CustomClassLoader.class.getClassLoader());
    }

    // violates parent-delegation pattern
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> clazz = findLoadedClass(name);
            if (clazz == null) {
                try {
                    clazz = findClass(name);
                } catch (ClassNotFoundException e) {
                }

                if (clazz == null) {
                    clazz = getParent().loadClass(name);
                }
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }
    }
}

ClassFileTransformer, используемый в моем агенте (с javassist):

public class MyTransformer implements ClassFileTransformer
{
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
    {
        byte[] byteCode = null;

        if (className.replace("/", ".").equals("com.example.services.TargetService"))
        {
            ClassPool cp = ClassPool.getDefault();
            CtClass cc;
            try
            {
                cc = cp.get("com.example.services.TargetService");
                CtMethod verifyMethod = cc.getDeclaredMethod("verify");
                //invalidate the verification process of method : verify
                verifyMethod.insertBefore("{return true;}");
                byteCode = cc.toBytecode();
                cc.detach();
                return byteCode;
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        return byteCode;
    }
}

Агент:

public class Agent
{
    public static void premain(String agentArgs, Instrumentation inst)
    {
        inst.addTransformer(new MyTransformer());
    }

}

Я придумал обходной путь, оснастив сам CustomClassLoader, вызвав toolsation.redifineClasses(), но не знаю, как передать экземпляр инструментария в экземпляр CustomClassLoader; Я новичок в загрузке инструментов/классов и до сих пор не совсем понимаю их механизм. Любая помощь? Спасибо.

Чтобы сделать это просто:

  1. Внутри приложения создайте собственный URLClassLoader, который во время выполнения загружает некоторые файлы jar в другое место в вашей файловой системе.
  2. Внедрите агент java, преобразующий класс, загруженный вашим загрузчиком классов, заменив один из его методов или что-то в этом роде.
  3. Внутри приложения назначьте экземпляр класса его интерфейсу и вызовите его инструментальные методы.
  4. Запустите приложение с параметром -javaagent для проверки.

person leshiv l    schedule 31.05.2016    source источник
comment
вы пробовали с простым агентом без зависимости? например, попробуйте просто вызвать System.out.println(className); в вашем методе преобразования затем верните classfileBuffer. Я попытался загрузить класс, используя ваш CustomClassLoader, и я вижу имя класса в своей консоли.   -  person Nicolas Filotto    schedule 31.05.2016
comment
@NicolasFilotto Проблема решена, и спасибо, вы напомнили мне об инструментах, которые я использовал с агентом. Сам агент получил уведомление о событии загрузки классов, но средство инструментирования не может получить байт-код, если внешний URL-адрес не добавлен в его путь к классам.   -  person leshiv l    schedule 01.06.2016


Ответы (1)


Я предполагаю, что ваш класс не оснащен должным образом, потому что вы вызываете ClassPool.getDefault(), который не включает файлы классов, видимые для вашего пользовательского загрузчика классов, а только для системного загрузчика классов. Вы никогда не регистрируете файл класса classfileBuffer.

В качестве альтернативы вы можете попробовать Byte Buddy, который предлагает более легкий доступ к инструментальному API:

new AgentBuilder.Default()
  .type(named("com.example.services.TargetService"))
  .transform((builder, type, loader) -> {
    builder.method(named("verify")).intercept(FixedValue.of(true));
  }).installOn(instrumentation);

Вышеупомянутый агент может быть вызван методом agentmain или premain. Вы также можете отключить изменения формата файла класса и переопределить существующие классы в случае, если класс уже (потенциально) загружен во время прикрепления.

person Rafael Winterhalter    schedule 31.05.2016
comment
Это проблема пула классов, добавьте cp.appendClassPath(new LoaderClassPath(loader)); и работает, большое спасибо, а byte-buddy выглядит круто. проголосовал бы, если бы у меня было достаточно представителей. - person leshiv l; 01.06.2016