2016-05-31 15 views
2

Ich habe versucht, den Byte-Code mehrerer Klassen zu ändern, deren Verpackungs-JAR-Dateien nicht im Klassenpfad sind - sie werden während einer Laufzeit mit einer URL ClassLoader geladen. Ich habe versucht, eine java agent mit ClassFileTransformer in der Hoffnung zu verwenden, diese Klassen abzufangen, aber gescheitert. Der Classloader ist Teil eines Legacy-Projekts und kann daher nicht direkt geändert werden.Wie instrumentieren Sie Klassen, die von einem benutzerdefinierten Klassenlader geladen wurden?

Der Agent eignet sich gut für Klassen, die von 'Local' von AppClassLoader geladen werden, ignoriert jedoch nur die vom benutzerdefinierten Classloader geladenen Klassen.

die 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; 
     } 
    } 
} 

die ClassFileTransformer in meinem Agenten verwendet (mit 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; 
    } 
} 

der -Agent:

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

} 

Ich habe einen Workaround gefunden, indem ich den CustomClassLoader selbst instrumentiert habe, indem ich instrumentation.redifineClasses() aufgerufen habe, aber nicht weiß, wie ich die Instrumentierungsinstanz an die CustomClassLoader-Instanz übergeben soll. Ich bin neu in der Instrumentierung/Klassenbelastung und immer noch nicht ganz klar mit ihrem Mechanismus. Irgendwelche Hilfe? Vielen Dank.

Um es einfach zu machen:

  1. Innerhalb einer App einen benutzerdefinierten URLClassLoader erstellen, das einige JAR-Dateien an anderer Stelle lädt in ihrem System während der Laufzeit-Datei.
  2. Implementieren Sie einen Java-Agenten, der eine Klasse transformiert, die von Ihrem Klassenlader geladen wurde, und dabei einen der Körper der Methode ersetzt.
  3. Innerhalb der App zuweisen Sie eine Klasse Instanz seiner Schnittstelle und rufen Sie seine instrumentierten Methoden.
  4. Führen Sie die App mit -Javaagent zu überprüfen.
+0

haben Sie versucht mit einem einfachen Agenten ohne Abhängigkeit? versuchen Sie zum Beispiel, einfach System.out.println (Klassenname) aufzurufen; in Ihrer Methode transform dann classfileBuffer zurückgeben. Ich habe versucht, eine Klasse mit Ihrem CustomClassLoader zu laden und ich sehe den Namen der Klasse in meiner Konsole –

+0

@NicolasFilotto Problem gelöst und danke, erinnern Sie mich an die Tools, die ich mit Agent verwendet. Der Agent selbst wurde über das Classloading-Ereignis benachrichtigt, aber das Instrumentierungswerkzeug kann den Bytecode nicht abrufen, wenn die externe URL nicht zum Klassenpfad hinzugefügt wurde. –

Antwort

1

Ich gehe davon aus, dass Ihre Klasse nicht richtig instrumentiert ist, weil Sie ClassPool.getDefault() fordern, die nicht Klasse enthält Dateien sichtbar zu Ihren benutzerdefinierten Klassenlader, sondern nur an die Systemklassenlader. Sie registrieren nie die classfileBuffer Klassendatei.

Als Alternative können Sie Byte Buddy ausprobieren, die einen leichteren Zugang zu den Instrumenten API bietet:

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

Das obige Mittel kann aus dem agentmain oder premain Methode aufgerufen werden. Sie können auch Änderungen am Klassendateiformat deaktivieren und vorhandene Klassen neu definieren, falls die Klasse bereits (möglicherweise) beim Anhängen geladen wurde.

+1

Es ist das Klassen-Pool-Problem, fügen Sie 'cp.appendClassPath (neue LoaderClassPath (loader));' und funktioniert, wirklich danke, und Byte-Buddy sieht cool aus. würde upvote, wenn ich genug rep haben würde. –