2016-05-31 10 views
2

Я пытался изменить байтовый код нескольких классов, чьи файлы 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, вызывающий instrumentation.redifineClasses(), но не знаю, как передать экземпляр инструментария в экземпляр CustomClassLoader; Я новичок в загрузке инструментов/классов и до сих пор не совсем понятен с их механизмом. Любая помощь? Благодарю.

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

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

Вы пробовали с простым агентом без зависимости? например, попробуйте просто вызвать System.out.println (className); в вашем методе transform затем возвращайте classfileBuffer. Я попытался загрузить класс, используя свой CustomClassLoader, и я вижу имя класса на моей консоли. –

+0

@NicolasFilotto Проблема решена и спасибо, вы напоминаете мне инструменты, которые я использовал с агентом. Агент сам получил уведомление о событии загрузки классов, но инструмент инструментария не может получить байт-код, если внешний URL не добавлен в его путь к классам. –

ответ

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. Вы также можете отключить изменения формата файла класса и переопределить существующие классы в случае, если класс уже (потенциально) загружен во время вложения.

+1

Это проблема с пулом классов, добавьте 'cp.appendClassPath (новый LoaderClassPath (loader));' и работает, действительно спасибо, а байт-приятель выглядит круто. если бы у меня было достаточно репутации. –