2017-03-29 1 views
1

一般的な考え方:動的にクラスを再読み込みして実装を変更できるようにするため、プログラム全体を再起動せずにメインアプリケーションを実行し、 。すべての外部コードは「モジュール」でグループ化され、各モジュールには「onEnable、postEnable、onDisable」というエントリ/出口ポイントを持つメインクラスがあり、任意の数のクラスで構成できます。モジュールをロードするには、エントリポイントを含むクラスが指定され、ロードされます。私はそれらを "モジュール"と "追加のクラス"として参照します。 "モジュール"は上記の関数を含むクラスです。 "public interface Module"、 "additional classes"は、モジュールが使用するすべてのものを指します。実行可能ではありませんが、それ自体でモジュールではありません(たとえば、 "Car implements Module"というモジュールがあり、そのモジュールには "Engine"クラスが必要です) - > "Car"はモジュール、 "Engine"私が最初にモジュールをロードするためにやっているの)URLClassLoader同じフォルダ内にXはありますが、Yは見つかりません

コード(名前はパス、後記の例)を含めた完全なクラス名を含む文字列です:

Class<?> clazz = mainLoader.loadClass(name); 
Module module = (Module) clazz.newInstance(); 
addLoadedModule(module); 
enableLoadedModule(module); 

そして、ここではそれがだとき、私はモジュールをリロードする方法ですすでに存在する私は実装をオーバーライドすることができます。 "m"は、再ロードされるはずのModuleの現在の実装のインスタンスです。

boolean differs = false; 
Class<?> newClass = null; 
try (URLClassLoader cl = new URLClassLoader(urls, mainLoader.getParent())) 
{ 
    // Try to load the class and check if it differs from the already known one 
    newClass = cl.loadClass(m.getClass().getName()); 
    differs = m.getClass() != newClass; 
} 
catch (IOException | ClassNotFoundException e) 
{ 
    // Class couldn't be found, abort. 
    e.printStackTrace(); 
    return; 
} 
if (!differs) 
{ 
    // New class == old class -> no need to reload it 
    return; 
} 
Module module = null; 
try 
{ 
    // Try to instantiate the class 
    module = (Module) newClass.newInstance(); 
} 
catch (InstantiationException | IllegalAccessException e) 
{ 
    // Can't instantiate, abort 
    e.printStackTrace(); 
    return; 
} 
// Check versions, only reload if the new implementation's version differs from the current one. Version is a custom annotation, don't worry about that; the version check works fine 
Version oldVersion = m.getClass().getAnnotation(Version.class); 
Version newVersion = module.getClass().getAnnotation(Version.class); 
if (oldVersion.equals(newVersion)) 
{ 
    return; 
} 
// And if everything went well, disable and remove the old module from the list, then add and enable the new module. 
disableModule(m); 
modules.remove(m); 
modules.put(module, false); 
enableLoadedModule(module); 

これはmainLoaderで、URLは[]負荷に外部のクラスを含む場所を指すURLです:

mainLoader = new URLClassLoader(urls, this.getClass().getClassLoader()); 

私はRE-ロード実装しようとすると問題があること、起こります

クラスAのモジュールには、クラスBが機能する必要があります。これは、クラスAを動的にロードしてリロードしようとしたときに起こります。

ロードA - > "確かに、Bが必要です。" - >自動的にBを読み込みます - > "これでいいのよ、Aは今はうまくいく" リロードA - > "もちろん、Bが必要です。" - >クラッシュBは

どちらのクラスがまったく同じフォルダに、このような構造に配置されている見つけることができなかったので:

  • クラスAモジュールを実装しています:COM/fooの/バー/ A.class
  • クラスB:COM/fooの/バー/ B.class
  • のURL:[ "COM/fooの/バー/"]

は、私は負荷の関数を呼び出す( "com.foo.bar.A" )は、最初にロードしようとすると動作しますが、リロードしようとすると失敗します。上記で説明した。

「単一クラスモジュール」をロードしようとするとうまく動作しますが、モジュールが追加の外部クラスに依存しているときに問題が発生します。私は別のクラスローダーを使用してURLClassLoaderのリロードプロセスの親として使用しようとしました。これらのクラスローダーは、sysloader、Module.class.getClassLoader()、mainLoader(その1つを使用して、既に新しいクラス定義を見つけることはありませんそのことを知っているからドライブから再びロードしようとすることさえありません)、mainLoader.getParent()、古いモジュールのクラスローダー、およびモジュールクラスローダーの親です。

私は恐らく何かを目の当たりにしているだけですが、最初に "余分な"クラスをロードするのはなぜか分かりませんが、ベースクラスをリロードすると失敗します...

デバッグ出力や正確なエラーが必要な場合は、デバッグ出力を何が何をするのかを説明するコメントに置き換えたので、何が起こっているのかについてかなり詳細なログが得られましたが、チェックとロードのプロセス全体がうまくいくので、モジュールを有効にしようとするとクラッシュします。モジュールの "onEnable"メソッドは追加のクラスBを必要とします、それは失敗するところです。私が言ったように、クラスAとクラスBの実装が必要な場合、モジュール、他のコードやデバッグ出力が私に知らせて、要求通りにそれらを追加します。

答えて

1

あなたが試すことができますいくつかあります:それはクラスをロードし、どのクラスローダがクラスをロードするために使用されたときに追跡できるように

  1. がするURLClassLoaderの拡張を作成します。
  2. あなたの他の問題は、これらのクラスが "default"クラスパスで利用できないことを確認して、そのバージョンを使用するようにします。クラスの親を最初にチェックするデフォルトのクラスローディング動作をオーバーライドしていません。
  3. あなたが直面している他の問題は、VMがクラスをキャッシュする方法に関連しています - 私はこれがどのように機能するかは完全にはわかりませんが、経験したことから、クラスが読み込まれると、クラスを再度ロードしないように、共有ストレージスペースを確保します。この共有スペースクラスは、ロードされたクラスローダーが到達不能になるまでアンロードされません。
+0

これは問題の「答え」ではありませんが、「この共有スペースクラスは、ロードされたクラスローダが到達不能になるまで、この共有スペースクラスはアンロードされません」というヒントが含まれています。 - >私のコードでは、クラスローダーはtry/catch節の間だけ存在し、最初は新しいモジュールのクラスを取得します。クラスをロードするとすぐに閉じられ、削除されるので、明らかにほかのクラスをロードできなくなります。私は今クラスローダーを保存し、新しいモジュールがロードされたときに閉じて、 "追加クラス"をロードすることができます – Pepich1851

+0

wow ...クラスローダーが範囲外になった問題 –

0

解決方法は、クラスローダーがtry/catch句にのみ存在するため、初期クラスのロードが完了するとすぐにクローズされ、削除されます。モジュールの新しい実装がロードされるまで、クラスローダーをマップに格納することで問題を解決しました。その後、古いローダーを破棄して新しいクラスを格納することができます。

関連する問題