外部ライブラリへの依存を減らすために (そして主に学習課題として)、開発中の教育用 Web アプリケーションに ServletContextListener を追加することにしました。「WEB-INF/classes」ディレクトリをスキャンして、特定の注釈を持つクラス名 (文字列として格納) のレジストリを構築します。
この一環として、最初に使用した WebappClassLoader を悪用してコンテキストがロードされたときに permgen がいっぱいになるのを防ぐために、頻繁に破棄できるカスタム ClassLoader を作成しました。
残念ながら、java.lang.NoClassDefFoundError: javax/websocket/server/ServerEndpointConfig$Configurator
ServerEndpointConfigurators の 1 つをロードしようとすると、厄介な例外が発生します。
public class MetascanClassLoader extends ClassLoader
{
private final String myBaseDir;
public MetascanClassLoader( final String baseDir )
{
if( !baseDir.endsWith( File.separator ) )
{
myBaseDir = baseDir + File.separator;
}
else
{
myBaseDir = baseDir;
}
}
@Override
protected Class<?> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
synchronized( getClassLoadingLock( name ) )
{
Class<?> clazz = findLoadedClass( name );
if (clazz == null)
{
try
{
final byte[] classBytes =
getClassBytesByName( name );
clazz = defineClass(
name, classBytes, 0, classBytes.length );
}
catch (final ClassNotFoundException e)
{
if ( getParent() != null )
{
clazz = getParent().loadClass( name );
}
else
{
throw new ClassNotFoundException(
"Could not load class from MetascanClassloader's " +
"parent classloader",
e );
}
}
}
if( resolve )
{
resolveClass( clazz );
}
return clazz;
}
}
private byte[] getClassBytesByName( final String name )
throws ClassNotFoundException
{
final String pathToClass =
myBaseDir + name.replace(
'.', File.separatorChar ) + ".class";
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try( final InputStream stream = new FileInputStream( pathToClass ) )
{
int b;
while( ( b = stream.read() ) != -1 )
{
baos.write( b );
}
}
catch( final FileNotFoundException e )
{
throw new ClassNotFoundException(
"Could not load class in MetascanClassloader.", e );
}
catch( final IOException e )
{
throw new RuntimeException( e );
}
return baos.toByteArray();
}
}
一部のコードは、デフォルトの ClassLoader 実装の影響を受けています。
私がやろうとしていることの一般的な流れは次のとおりです。
- ロードしようとしているクラス名のロックを取得して、他のスレッド (将来のある時点でこの ClassLoader を並列実行でヒットしようとした場合) が同じクラスを 2 回ロードしようとしないようにします。
- findLoadedclass(); を使用して、このクラスが既に読み込まれているかどうかを確認します。
- まだロードされていない場合は、「WEB-INF/classes」ディレクトリからクラスを取得してみてください。
- これが失敗した場合は、親クラスローダーに委譲します。
- それでもだめなら爆破(投げる)。
クラス ファイルをスキャンした後、クラス名を正しく渡していることを保証できます。これは、MetascanClassLoader.load( className ) 呼び出しを Class.forName( className ) に置き換えると完全に機能しますが、前述のように、そうしたくありません。パーマを叩きます。
Tomcat でのみパッケージ化されているクラスへの参照を含むクラスをロードしようとしたときにのみフォールオーバーするようです。Java SE クラスではまったく問題ありません。
機能しない原因以外に、私が行っている特に陰湿な/不快なことに気付いた場合はお知らせください.
UPDATE : スーパークラスのデフォルト コンストラクターを使用すると、親クラスローダーがシステム クラスローダーに設定されるようです。これにより、Tomcat で見つかった欠落クラスが説明されます。
コンストラクターの最初の行として次の行を追加しました。
super( Thread.currentThread().getContextClassLoader() );
残念ながら、ClassLoader によって返されるすべての Class オブジェクトが空になり、クラス名以外の情報がないため、まだ問題が発生しています。(これを発見するために、Eclipse を使用して内部フィールドを検査しました。)
詳細情報: Java SE クラスは、オブジェクト インスペクションにより、引き続き正しくロードされています。WEB-INF\classes にあるクラスの 1 つを調べると、loadClass() が呼び出された後の任意の時点でクラス オブジェクトを調べようとすると、このような動作になります (パッケージ名を非表示にして不要なプロジェクト情報を共有しないようにします)。
.
また、loadClass( name, resolve ) の解決を true にハードコーディングすることによって resolveClass() が呼び出されるようにしようとしましたが、これは違いはありません。
再度更新Class
: 以下の Holger の優れた「マスを平手打ち」の瞬間のおかげで、返されるオブジェクトのプライベート変数の意味を推測できると考えるのは非常に愚かでした。
System.out.print()
私は自分の中にいくつかの sを投げましたClassLoader
(synchronized
もちろん、同期なしで初めて試したときは非常に面倒でした!)のreturnステートメントの直前に次の行を貼り付けましたloadClass()
System.out.print( clazz.getName() + " annotations:" );
for( final Annotation a : clazz.getAnnotations() )
{
System.out.print( " " + a.annotationType().getName() + ";" );
}
System.out.println();
これにより、期待していた結果が得られ、次の行に沿って何かが出力されました。
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;
しかし、ちょっと待ってください - それは機能していますか?
System.out.print( clazz.getName() + " annotations:" );
for( final Annotation a : clazz.getAnnotations() )
{
System.out.print( " " + a.annotationType().getName() + ";" );
}
System.out.print( "HAS_ANNOTATION:" );
if( clazz.getAnnotation( MyAnnotationOne.class ) != null )
{
System.out.print( "true" );
}
else
{
System.out.print( "false" );
}
System.out.println();
結果:
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;HAS_ANNOTATION:false
うわぁ!しかし、それは私を襲った。以下の私の答えを確認してください。