BeanBoxでは、SimpleClassLoadeという名前のクラス・ローダが、名前に反して、 結構複雑な仕事を一手に引き受けています。ローダを解読するには基本があります。 それは、loadClassメソッドの働きをしっかりと把握することです。
=============================================================================
public class SimpleClassLoader extends ClassLoader {
public final static String urlPrefix = "SIMPLE";
private static final String protocolPathProp = "java.protocol.handler.pkgs";
private static boolean debug = false; // debugging?
private String cookie; // name of the jar file
// all instances, by cookie. We really only instantiate one, see next
private static Hashtable loaders = new Hashtable();
// The only SimpleClassLoader we actually instantiate
public static SimpleClassLoader ourLoader;
// Additional directory from where to look for resources...
// (after CLASSPATH and loaded JARs).
// -- currently unused --
private String localResourceDirectory;
// Overrides for local resources
private Hashtable localOverrides = new Hashtable();
// Resource Hash
private Hashtable resourceHash = new Hashtable();
// Mime type hash
private Hashtable mimeHash = new Hashtable();
// Used to remember bytecodes, and local classes
private Hashtable bytecodes = new Hashtable();
/**
* Initialization of statics
*/
static {
// Add this protocol type to the http properties
Properties newP = new Properties(System.getProperties());
newP.put(protocolPathProp,
newP.getProperty(protocolPathProp)+"|sun.beanbox");
System.setProperties(newP);
// *The* loader instance
ourLoader = createLoader("BeanBox", null);
}
}
=============================================================================
次のリストをみてください。ClassLoaderクラスは、すでにロードしたクラスを 格納するハッシュ・テーブルを持っていますので、このメソッドが最初に行うことは、 指定されたクラスが、既にロードされていないかをチェックすることです。 ロードされていなかったら、loadClassは、JARファイルからそのクラスをロードしようとします。この時、呼ばれるのが、applyDefinitionというメソッドです。 そのクラスがJARファイルにもなかったら、最後に、CLASSPATH上にある、システム・ クラスを探しに行きます。
=============================================================================
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class cl = findLoadedClass(name);
if (cl == null) {
cl = applyDefinition(name, resolve);
}
if (cl == null) {
try {
cl = findSystemClass(name);
return cl;
} catch (ClassNotFoundException e) {
}
}
if (cl == null) {
throw new ClassNotFoundException(name);
}
if (resolve) {
resolveClass(cl);
}
return cl;
}
=============================================================================
applyDefinitionが、直接にJARファイルを読むわけではありません。applyDefinition は、既にハッシュ・テーブルに入れられたバイト列から、defineClassとresolveClassを 使って、クラスを生成します。applyDefinitionには、引数にクラスのベクターをとる もう一つのメソッドがあって、ベクター上のすべてのクラス名に対して、先の applyDefinitionを呼び出します。
=============================================================================
private Class applyDefinition(String name, boolean resolve) {
byte buf[] = (byte []) bytecodes.get(name);
if (buf == null) {
return null;
} else {
Class c = super.defineClass(null, buf, 0, buf.length);
if (c != null && resolve) {
resolveClass(c);
}
.......
return c;
}
}
public synchronized void applyDefinitions(Vector classList) {
for (Enumeration k = classList.elements(); k.hasMoreElements();) {
String classname = (String) k.nextElement();
Class c = findLoadedClass(classname);
if (c == null) {
applyDefinition(classname, true);
}
}
}
=============================================================================
JARファイル自体の読み込みは、JarLoaderクラスのloadJarメソッドがその主要部分を 構成しています。このメソッドは、また、java.util.zipパッケージを利用して、 一つにまとめられたJARファイルから、その構成要素を取り出す、いいサンプルに なっています。
JARファイルのInputStreamは、ZipInputStreamに変えられます。このZipInputStream に対して、getNextEntryメソッドで、構成要素のエントリ ZipEntryを獲得することが 出来ます。エントリー情報を一つ獲得したら、こんどは、1024バイトの バッファー buffer経由で、ByteArrayOutStreamに、そのエントリーのデータ部分を読み込みます。 ByteArrayOutStreamは、toByteArrayメソッドでバイト列 bufに変換されています。 少し面白いのは、このデータのContentTypeを調べるために、このバイト列を、一時的に ストリームにして、その中身を、guessContentTypeFromStreamメソッドで調べている ことです。
その後の処理は、そのタイプによって分かれるのですが、ここでは、そのデータが、 クラスである場合を、見てみることにしましょう。読み込まれたバイト列は、先に見た SimpleClassLoaderのJARファイルからのクラス・データを格納するハッシュ・テーブル に格納されます。これが、loader.setDefinition(classname, buf)です。 ついで、このクラス名は、JarLoaderクラスのベクターclassListに追加されます。
こうした処理が、ZipInputStream上にZipEntryがなくなるまで繰り返されます。
=============================================================================
public JarInfo loadJar() {
// load all resources.
// may raise an exception if the file is not a Jar file.
ZipInputStream zis = null;
Manifest mf = null;
Vector classList = new Vector(); // the classes
Vector serList = new Vector(); // the serialized objects
byte buffer[] = new byte[1024];
try {
zis = new ZipInputStream(jarStream);
ZipEntry ent = null;
while ((ent = zis.getNextEntry()) != null) {
String name = ent.getName();
String type = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (;;) {
int len = zis.read(buffer);
if (len < 0) {
break;
}
baos.write(buffer, 0, len);
}
byte[] buf = baos.toByteArray();
int size = buf.length;
debug("loading "+name);
if (Manifest.isManifestName(name)) {
type = "manifest/manifest";
}
if (type == null) {
InputStream tmpStream = new ByteArrayInputStream(buf);
type = guessContentTypeFromStream(tmpStream);
tmpStream.close();
}
if (type == null) {
type = "input-stream/input-stream";
}
// Always make the data available as a local stream.
loader.putLocalResource(name, buf, type);
if (type.startsWith("application/java-serialized-object")) {
String sername = name.substring(0, name.length() - 4);
sername = sername.replace('/', '.');
serList.addElement(sername);
} else if (type.startsWith("application/java-vm")) {
/* remove the .class suffix */
String classname = name.substring(0, name.length() - 6);
classname = classname.replace('/', '.');
loader.setDefinition(classname, buf);
classList.addElement(classname);
} else if (type.equals("manifest/manifest")) {
mf = new Manifest(buf);
} else {
debug("ZipEntry " + name +
" has unsupported mimetype " + type);
}
}
} catch (IOException e) {
debug("IOException loading archive: " + e);
e.printStackTrace();
} catch (Throwable ex) {
debug("Caught "+ex+" in loadit()");
ex.printStackTrace();
} finally {
if (zis != null) {
try {
zis.close();
} catch (Exception ex) {
// ignore
}
}
}
loader.applyDefinitions(classList);
JarInfo ji = createJarInfo(classList, serList, mf);
return ji;
}
=============================================================================
最後に、loader.applyDefinitions(classList)という処理を説明してみましょう。 ベクターを引数とするapplyDefinitionsの呼び出しは、そのベクターを構成する クラスに対するapplyDefinitions呼び出しの繰り返しになることは、前に説明し ました。問題は、これがどのような副作用を持つのかということです。 このapplyDefinitionのソースの中の、 Class c = super.defineClass(null, buf, 0, buf.length); というところに注目して下さい。SimpleClassLoaderのsuperクラスは、 ClassLoaderですので、このメソッド呼び出しは、次のリストで示したdefineClass メソッドの呼び出しに他なりません。ここでは、定義されたクラスが、classesという ハッシュ・テーブルに入れられることがポイントです。このclassesハッシュ・テーブル は、先に見たfindLoadedClassメソッドで、最初にチェックされるテーブルです。 要するに、applyDefinitionの呼び出しは、SimpleClassLoaderの中のテーブルに JARファイルに含まれているクラスを登録するという効果を持つのです。
=============================================================================
public abstract class ClassLoader {
private boolean initialized = false;
private Hashtable classes = new Hashtable();
.....
.....
protected final Class defineClass(String name,
byte data[], int offset, int length) {
check();
Class result = defineClass0(name, data, offset, length);
if (result != null)
classes.put(result.getName(), result);
return result;
}
.....
}
=============================================================================