1

実行時に別のクラスが使用できるカスタムクラスを作成したいとしましょう。インスツルメントされたクラスの読み込みを避ける

package redefineconcept; 

import net.bytebuddy.ByteBuddy; 
import net.bytebuddy.dynamic.DynamicType; 
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; 

import java.lang.reflect.InvocationTargetException; 

public class LoadingTest { 
    public static final String HelloWorldTag = "$HelloWorld"; 

    public static void main(String[] args){ 
     new LoadingTest().run(); 
    } 

    private void run(){ 

     InstanceUser u = new InstanceUser(); 
     u.start(); 

     Class <?> createdClass = createAndLoadFor(InstanceUser.class); 
     System.out.println(String.format("created new class %s", createdClass.getName())); 
     InstanceUser.canAccess = true; 

     try { 
      u.join(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 

    private Class<?> createAndLoadFor(Class<?> clazz){ 
     ByteBuddy byteBuddy = new ByteBuddy(); 

     String newClassName = clazz.getName() + LoadingTest.HelloWorldTag; 

     DynamicType.Builder builder = byteBuddy 
       .subclass(Object.class) 
       .name(newClassName) 
       ; 

     DynamicType.Unloaded<?> newType = builder.make(); 

     return newType 
       .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION) 
       .getLoaded(); 
    } 
} 

class InstanceUser extends Thread{ 
    public static volatile boolean canAccess = false; 
    Object instance; 

    @Override 
    public void run() { 
     while(!canAccess){} 
     String cn = this.getClass().getName() + LoadingTest.HelloWorldTag; 
     Class clazz; 
     try{ 
      clazz = Class.forName(cn); 
     }catch(ClassNotFoundException e){ 
      e.printStackTrace(); 
      throw new RuntimeException(); 
     } 
     try{ 
      instance = clazz.getConstructor().newInstance(); 
     }catch(NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e){ 
      e.printStackTrace(); 
      throw new RuntimeException(); 
     } 
    } 
} 

これは機能します。しかし

the ByteBuddy tutorialは、あなたが一度に一つの動的な型を作成しているので、マイナーな関連性があると循環依存関係に遭遇する可能性を検討するかもしれない

を示唆しています。ただし、タイプの動的作成によって、いわゆる補助タイプの作成がトリガーされる可能性があります。

これらのタイプは、作成中のダイナミックタイプへのアクセスを提供するために、自動的にByte Buddyによって作成されます。

このため、可能であれば、既存のClassLoaderを注入するのではなく、特定のClassLoaderを作成して動的に作成したクラスをロードすることをお勧めします。

私は、クラスローダーやByteBuddyについてはあまり知らないですが、このチュートリアルではクラスローダーが階層的に注文されているようです。

もしそうなら、新しいクラスローダをclazz.getClassLoader()にチェーンすることは可能でしょうか?

まあ、私はClassLoadingStrategy.Default.WRAPPERでもClassLoadingStrategy.Default.CHILD_FIRSTでもこのような運がなかった。

通常、Javaクラスローダーが直接指定された名前のタイプをロードしようとする前に、親クラスローダを照会することを信じるように私を導い

created new class redefineconcept.InstanceUser$HelloWorld 
java.lang.ClassNotFoundException: redefineconcept.InstanceUser$HelloWorld 

内の両方の結果。

は、彼らが唯一のクエリ親 ClassLoaderのではなく、子どもたち を意味します。

そうですか?

ClassLoadingStrategy.Default.INJECTIONをここで使用することは避けられますか?

+0

とにかく 'Class.forName(cn)'を使用しているので、なぜクラスローダーの解決戦略を変更しようとしていますか?カスタムクラスローダーを使って、それを渡し、 'Class.forName(cn)'を 'customClassLoader.loadClass(cn)'に置き換えてください... – Holger

答えて

0

クラスローダーは(通常)階層的です。 INJECTION戦略を使用している場合、Byte Buddyはクラスを明示的に定義してタイプごとに手動で定義します。 JVMとクラスローダに応じて、クラスローディングが発生する可能性があります。

AがBおよびB参照を参照する状況を考えてみましょう。バイトバディがAの前にAを注入すると、Aの注入によってその時点でまだ注入されていないBがロードされることがあります。この時点で、注入の対象となるクラスローダは、Bがルックアップしようとすると、失敗して失敗します。NoClassDefFoundErrorで失敗します。

WRAPPER戦略を使用する場合、Byte Buddyは、両方のタイプを認識し、注入が不要なためにAがロードされたときにBをルックアップできる新しいクラスローダーを作成します。

あなたが遭遇する問題は、Class.forName(name)の使用によって発生します。このメソッドは、呼び出し側のクラスローダーが使用されていることを意味する呼び出し側に依存します。あなたのスレッドから、これはあなたが以前に注入したのと同じクラスローダーであるシステムクラスローダーになるでしょう。

しかし、通常、JVMは遅延型をロードしていて、注入はすべてのユースケースの99%で大きな問題にはならないはずです。

関連する問題