2.8 Oracle DatabaseのClass.forName()

JLSでは、Class.forName()について次のように記述されています。

クラスの完全修飾名を指定すると、このメソッドはクラスを検索、ロードおよびリンクしようとします。正常に実行されると、このクラスのClassオブジェクトへの参照を戻します。正しく実行されなかった場合は、ClassNotFoundExceptionのインスタンスがスローされます。

クラスの参照は、参照側のクラスにかわって常にClassLoaderのインスタンスを介して実行されます。Java Development Kit(JDK)実装とOracle JVM実装の違いは、クラスの検索方法です。

  • JDKは、ClassLoaderの1つのインスタンスを使用して、環境変数CLASSPATHに指定されている一連のディレクトリ・ツリー・ルートを検索します。

  • Oracle JVMは、クラスの検索方法を指定する複数のリゾルバを定義します。各クラスにはリゾルバが対応付けられており、各クラスは異なるリゾルバを保持できます。Class.forName()をコールするメソッドを実行すると、現在実行中のクラス(this)のリゾルバを使用してクラスが検索されます。

予定にないリゾルバを使用してクラスを検索しようとすると、間違った結果となります。たとえば、スキーマXのクラスXがスキーマYのクラスYに対して、クラスZを参照するように要求する場合は、クラスXのリゾルバを使用しようとするとエラーになります。参照を実行しているのはクラスYであるため、クラスZの検索にはクラスYに対応付けられたリゾルバが使用されます。つまり、クラスが別のスキーマに入っているときに、別のクラスの異なるリゾルバを指定すると(異なるスキーマにクラスが存在する場合はデフォルトでこの状態になります)、クラスを検索できない場合があります。

このようなリゾルバの問題は、次の方法で解決できます。

  • Classオブジェクト自体を指定してクラス名の参照を回避します。

  • Class.forName()メソッドに対してClassLoaderインスタンスを指定します。

  • classForNameAndSchema()メソッドに対してクラスとそのクラスが常駐するスキーマを指定します。

  • ClassForName.lookupClass()に対してスキーマとクラス名を指定します。

  • スキーマ名とクラス名でオブジェクトをシリアライズします。

注意:

システム・クラスがClass.forName()を起動すると、予期しない動作が発生することがあります。目的のクラスがSYSまたはPUBLICに常駐している場合のみ、そのクラスが検索されます。クラスがSYSまたはPUBLICのいずれにも常駐していない場合は、そのクラスに対してPUBLICシノニムを宣言できます。

この項の内容は次のとおりです。

2.8.1 Class.forName()へのClassLoaderの指定

Oracle Databaseは、スキーマ内でのクラスの検索にリゾルバを使用します。各クラスには指定のリゾルバが対応付けられており、各クラスには異なるリゾルバを対応付けることができます。したがって、クラスの検索は対応付けられたリゾルバの定義によって決まります。ClassLoaderインスタンスは、指定されたクラスに基づいて検索に使用するリゾルバを判断します。Class.forName()ClassLoaderインスタンスを指定すると、クラスは、そのクラスのリゾルバに定義されているスキーマ内で検索されます。このClass.forName()の改良型に対する構文は次のとおりです。

Class.forName (String name, boolean initialize, ClassLoader loader);

次の例は、現行のクラス・インスタンスまたはコール側のクラス・インスタンスのクラス・ローダーを指定する方法を示します。

例2-1 現行のクラスからのリゾルバの取得

Class.getClassLoader()メソッドを使用することで、任意のインスタンスのクラス・ローダーを取得できます。次の例は、インスタンスxで表されたクラスのクラス・ローダーを取得します。

Class c1 = Class.forName (x.whatClass(), true, x.getClass().getClassLoader());

例2-2 コール側クラスからのリゾルバの取得

oracle.aurora.vm.OracleRuntime.getCallerClass()メソッドを使用することで、実行中のメソッドをコールしたインスタンスのクラスを取得できます。クラスを取得した後は、戻されたクラスのClass.getClassLoader()メソッドをコールします。次の例では、workForCaller()メソッドをコールしたインスタンスのクラスを取得します。次に、そのクラス・ローダーを取得し、Class.forName()メソッドに渡します。結果として、クラスの検索に使用されたリゾルバがコール側クラスのリゾルバとなります。

void workForCaller()
{
  ClassLoader c1=oracle.aurora.vm.OracleRuntime.getCallerClass().getClassLoader();
  ...
  Class c=Class.forName(name, true, c1);
  ...
}

2.8.2 classForNameAndSchema()へのクラス名とスキーマ名の指定

検索するスキーマを特定できるリゾルバを指定することで、クラスを検索する位置の問題を解決できます。または、クラスがロードされているスキーマを指定できます。クラスのロード先スキーマが判明している場合は、Oracle Databaseが提供するDbmsJavaクラスにある、classForNameAndSchema()メソッドを使用できます。このメソッドには、クラス名とそのクラスが常駐するスキーマ名の両方を指定し、このメソッドによって、指定されたスキーマでのクラスの位置が特定されます。

例2-3 スキーマ名とクラス名の指定

次の例は、save()メソッドを使用してスキーマ名とクラス名を保存する方法を示しています。クラス名とスキーマ名が取得され、DbmsJava.classForNameAndSchemaメソッドを使用してそのクラスの位置が特定されます。

import oracle.aurora.rdbms.ClassHandle;
import oracle.aurora.rdbms.Schema;
import oracle.aurora.rdbms.DbmsJava;

void save (Class c1)
{
  ClassHandle handle = ClassHandle.lookup(c1);
  Schema schema = handle.schema();
  writeName (schema.getName());
  writeName (c1.getName());
}

Class restore()
{
  String schemaName = readName();
  String className = readName();
  return DbmsJava.classForNameAndSchema (schemaName, className);
}

2.8.3 lookupClass()へのクラス名とスキーマ名の指定

スキーマ名とクラス名が入った1つのString値をoracle.aurora.util.ClassForName.lookupClass()メソッドに指定できます。このメソッドをコールすると、指定されたスキーマでそのクラスの位置が特定されます。文字列は次の形式で指定する必要があります。

"<schema>:<class>"

たとえば、HRスキーマでcom.package.myclassの位置を特定するには、次のコードを使用します。

oracle.aurora.util.ClassForName.lookupClass("HR:com.package.myclass");

注意:

スキーマ名には大文字を使用してください。この場合、スキーマ名は大/小文字が区別されます。

2.8.4 シリアライズでのクラス名とスキーマ名の指定

クラスのシリアライズを解除すると、処理の一部として、名前に基づいてクラスが検索されます。検索が正常に行われるためには、シリアライズされたオブジェクトにクラス名とスキーマ名の両方が含まれている必要があります。

Oracle Databaseでは、オブジェクトをシリアライズまたはシリアライズを解除する際に、次のクラスを使用できます。

  • oracle.aurora.rdbms.DbmsObjectOutputStream

    このクラスはjava.io.ObjectOutputStreamを拡張し、適切な場所にスキーマ名を追加します。

  • oracle.aurora.rdbms.DbmsObjectInputStream

    このクラスはjava.io.ObjectInputStreamを拡張し、DbmsObjectOutputStreamによって書き込まれたストリームを読み取ります。このクラスはあらゆる環境で使用できます。Oracle Databaseで使用した場合は、スキーマ名が読み取られ、クラスを検索する際に使用されます。クライアントで使用した場合、スキーマ名は無視されます。

2.8.5 Class.forNameの例

次に、クラスを参照するいくつかのメソッドの例を示します。

import oracle.aurora.vm.OracleRuntime;
import oracle.aurora.rdbms.Schema;
import oracle.aurora.rdbms.DbmsJava;

public class ForName
{
  private Class from;
  
  /* Supply an explicit class to the constructor */
  public ForName(Class from)
  {
    this.from = from;
  }
  
  /* Use the class of the code containing the "new ForName()" */
  public ForName()
  {
    from = OracleRuntime.getCallerClass();
  }

  /* lookup relative to Class supplied to constructor */
  public Class lookupWithClassLoader(String name) throws ClassNotFoundException
  {
    /* A ClassLoader uses the resolver associated with the class*/
    return Class.forName(name, true, from.getClassLoader());
  }

  /* In case the schema containing the class is known */
  static Class lookupWithSchema(String name, String schema)
  {
    Schema s = Schema.lookup(schema);
    return DbmsJava.classForNameAndSchema(name, s);
  }
}

この例では、次のメソッドを使用してクラスを検索します。

  • インスタンスのクラスのリゾルバを使用するには、lookupWithClassLoader()をコールします。このメソッドによって、from変数のClass.forName()メソッドにクラス・ローダーが指定されます。from変数に指定されたクラス・ローダーは、このクラスにデフォルト設定されます。

  • 特定のクラスのリゾルバを使用するには、明示したクラス名の後にlookupWithClassLoader()を指定してForName()をコールします。ForName()メソッドによって、from変数が指定のクラスに設定されます。lookupWithClassLoader()メソッドは指定されたクラスのクラス・ローダーを使用します。

  • コール側クラスのリゾルバを使用するには、最初に、パラメータを指定せずにForName()メソッドをコールします。これにより、from変数がコール側クラスに設定されます。次に、lookupWithClassLoader()メソッドをコールし、コール側クラスのリゾルバを使用してクラスを検索します。

  • スキーマを指定してクラスを検索するには、lookupWithSchema()メソッドをコールします。このメソッドは、classForNameAndSchema()メソッドにクラス名とスキーマ名を渡します。