2017-07-26 4 views
1

J'écris une application en utilisant le JVMTI. J'essaye d'instrument le bytecode: en injectant des appels de méthode sur chaque entrée de méthode.Résolution de dépendances dans JNI DefineClass

Je sais comment faire cela, mais le problème est dans la classe d'instrument, disons qu'il s'appelle Proxy, que je charge en utilisant la fonction JNI DefineClass. Mon Proxy a quelques dépendances dans la bibliothèque de classes Java, actuellement juste java.lang.ThreadLocal<Boolean>.

Maintenant, dire que j'ai cela, où inInstrumentMethod est une boolean simple:

public static void onEntry(int methodID) 
{ 
    if (inInstrumentMethod) { 
     return; 
    } else { 
     inInstrumentMethod = true; 
    } 

    System.out.println("Method ID: " + methodID); 

    inInstrumentMethod = false; 
} 

Le code compile et fonctionne. Cependant, si je fais inInstrumentMethod un java.lang.ThreadLocal<Boolean>, j'obtiens un NoClassDefFoundError. Le code:

private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<Boolean>() { 
     @Override protected Boolean initialValue() { 
      return Boolean.FALSE; 
     } 
    }; 

public static void onEntry(int methodID) 
{ 
    if (inInstrumentMethod.get()) { 
     return; 
    } else { 
     inInstrumentMethod.set(true); 
    } 

    System.out.println("Method ID: " + methodID); 

    inInstrumentMethod.set(false); 
} 

Je pense que les dépendances ne sont pas résolus correctement et java.lang.ThreadLocal n'a pas été chargé (et ne pouvaient donc pas être trouvés). La question est, alors, comment puis-je forcer Java à charger java.lang.ThreadLocal? Je ne pense pas que je pourrais utiliser DefineClass dans ce cas; Y a-t-il une alternative?

+0

Vous obtenez 'NoClassDefFoundError' pour quelle classe? – EJP

Répondre

1

Je ne pense pas qu'il y ait un problème résoudre la classe standard java.lang.ThreadLocal, mais plutôt avec la classe interne PROLONGER, générée par

new ThreadLocal<Boolean>() { 
    @Override protected Boolean initialValue() { 
     return Boolean.FALSE; 
    } 
}; 

La résolution de ce via DefineClass pourrait en effet être impossible en raison de la circulaire dépendance entre la classe interne et externe, donc il n'y a pas d'ordre qui permet de les définir, sauf si vous avez un ClassLoader à part entière qui retourne les classes à la demande.

La solution la plus simple est d'éviter la génération d'une classe interne du tout, ce qui est possible avec Java 8:

private static ThreadLocal<Boolean> inInstrumentMethod 
            = ThreadLocal.withInitial(() -> Boolean.FALSE); 

Si vous utilisez une version antérieure à Java 8, vous ne pouvez pas l'utiliser que façon, donc la meilleure solution dans ce cas, consiste à réécrire le code pour accepter la valeur par défaut de null comme valeur initiale, ce qui élimine la nécessité de spécifier une valeur initiale différente:

private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<>(); 

public static void onEntry(int methodID) 
{ 
    if (inInstrumentMethod.get()!=null) { 
     return; 
    } else { 
     inInstrumentMethod.set(true); 
    } 

    System.out.println("Method ID: " + methodID); 

    inInstrumentMethod.set(null); 
} 

Vous pouvez également convertir cette interne anonyme classe à une classe de premier niveau. Depuis lors, cette classe n'a aucune dépendance à ce qui était auparavant sa classe externe, définissant ce sous-type de ThreadLocal d'abord, avant de définir la classe qui l'utilise, devrait résoudre le problème.

+0

Merci. Vous avez parfaitement raison! J'ai fait de la classe interne une classe appropriée et ça a bien fonctionné. – xuq01