- - PR -
Jar ファイルからのクラス読み込み処理
1|2|3|4|5
次のページへ»
投稿者 | 投稿内容 | ||||||||
---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2006-12-10 10:55
こんにちは。
jarファイルの中のクラスファイルを読んでくる処理のところで、クラスがあるにも関わらずClassNotFoundの例外が出てしまいます。 どうやら、classLoaderのところで、うまく読み込めていないようなのですが、どうすればいいのかわかりません。 ちなみに、下のようなエラーが出ます。 Exception in thread "main" java.lang.ClassNotFoundException: A at asm.Adapt.loadClass(Adapt.java:52) at java.lang.ClassLoader.loadClass(Unknown Source) at asm.Adapt.main(Adapt.java:139) Caused by: java.io.IOException: Class not found at org.objectweb.asm.ClassReader.readClass(Unknown Source) at org.objectweb.asm.ClassReader.<init>(Unknown Source) at asm.Adapt.loadClass(Adapt.java:46) ... 2 more ソースコードは以下の通りです。ご教授よろしくお願いします。 public class Adapt extends ClassLoader { private static final class TestClassLoader extends ClassLoader{ private final String className; private final ClassLoader cl; public TestClassLoader(ClassLoader cl, String className){ super(); this.cl = cl; this.className = className; } public Class loadClass(String name) throws ClassNotFoundException { if (className.equals(name)) { try{ byte[] bytecode = transformClass(className); // returns the adapted class return super.defineClass(className, bytecode, 0, bytecode.length); }catch(IOException ex){ throw new ClassNotFoundException("Load error: " + ex.toString(), ex); } } return cl.loadClass(name); } private byte[] transformClass(String className) throws IOException{ // gets an input stream to read the bytecode of the class String resource = className.replace('.', '/') + ".class"; InputStream is = getResourceAsStream(resource); byte[] b; ClassWriter cw = new ClassWriter(true, true); NotifierClassVisitor ncv = new NotifierClassVisitor(cw); ClassReader cr = new ClassReader(is); cr.accept(ncv, false); return cw.toByteArray(); // stores the adapted class on disk try { FileOutputStream fos = new FileOutputStream(resource + ".adapted"); fos.write(b); fos.close(); } catch (Exception e) { } return b; } } public static void main(final String args[]) throws Exception { JarFile jarfile = new JarFile(args[0]); for(Enumeration en = jarfile.entries(); en.hasMoreElements();){ JarEntry entry = (JarEntry)en.nextElement(); if(entry.getName().endsWith(".class")){ String className = entry.getName().replaceAll("/", "\\\\.").replaceAll("\\\\.class", ""); // loads the application class (in args[0]) with an Adapt class loader ClassLoader loader = new TestClassLoader(className.getClass().getClassLoader(), className); Class c = loader.loadClass(className); // calls the 'main' static method of this class with the // application arguments (in args[1] ... args[n]) as parameter Method m = c.getMethod("main", new Class[] { String[].class }); String[] applicationArgs = new String[args.length - 1]; System.arraycopy(args, 1, applicationArgs, 0, applicationArgs.length); m.invoke(null, new Object[] { applicationArgs }); } } } } | ||||||||
|
投稿日時: 2006-12-10 12:01
突っ込みどころが色々あるのですが、
ClassLoader loader = new TestClassLoader(className.getClass().getClassLoader(), className); システムクラスローダで読み込まれたクラスのクラスローダを取得するときは、nullが返るようになっているので、Stringクラスのクラスローダを取得しても、戻り値はnullになります。 InputStream is = getResourceAsStream(resource); は、親クラスローダに委譲していますが、コンストラクタでsuper()ってやっているので、リソースの解決はできない思います。nullが戻ってきていませんか? 実行環境がないので、「おかしいのでは?」というレベルの話ですが、コードを見る限り、単にデバッグしてないだけに思えます。ステップ実行くらいは最低限やってみましょう。 あと、この手のややこしいコードを貼り付けるときはBBコードを使いましょう。読むのが大変です。 | ||||||||
|
投稿日時: 2006-12-10 12:15
ClassLoaderを直接拡張しているので、親クラスローダのクラスパスにjarが 入っていない限り、getResourceAsStream(resource)で取得するリソースも 自分で解決しないといけません。 他にも怪しいところはありますが。 #同じインデントレベルでreturn cw.toByteArray()した後も #コードが続いてるのはなぜでしょう?コンパイルエラーでは?
なぜ二つのクラスがClassLoaderを拡張しているのか意味不明ですが、 java.net.URLClassLoaderを使えば自力でjarを扱わずに済むはずです。 ClassLoader#loadClass(String)しかオーバーライドせず、 実働メソッドのloadClass(String, boolean)やfindClass(String)を オーバーライドしないのは非常に危なっかしい気がします。 | ||||||||
|
投稿日時: 2006-12-11 02:07
>かつのりさん
InputStreamのところは、ご指摘どおり、nullが返ってきてしまいます。 それは、super()がいけないということですか? リソースの解決にならないということですが、どうしたらいいのか、わかりません。 >あしゅさん リソースを自分で解決するというのは、どういうことなのでしょうか。 申し訳ありませんが、もう少し具体的にお話いただけると幸いです。 return cw.toByteArray() のところは、正しくは、b = cw.toByteArray()でした。すみません。 引き続き、もう一度チェックしながら作り直していますが、やはりClassReaderの部分でエラーが出続けています。 それは、かつのりさんやあしゅさんのおっしゃったとおり、リソースの問題なのですが、どう変更すべきなのかがいまいちわかりません。 引き続きのご教授をどうかよろしくおねがいします。 | ||||||||
|
投稿日時: 2006-12-11 10:26
java.lang.ClassLoaderを継承してsuper()とやると、
親クラスローダがシステムクラスローダになります。 システムクラスローダがリソースを取得する対象は、一般的に言われる実行時クラスパスです。 で、オーバーライドされていないgetResource系のメソッドは、親クラスローダに委譲します。 親クラスローダで解決出来ない場合に実行時クラスパスを参照して駄目ならnullを返します。 一般的にクラスローダを自作するときには、 1.リソースの取得系のメソッドは、リソースの取得方法を解決する 2.クラスの読み込み系のメソッドは、委譲方法をカスタムする というように実装します。 ヒラさんが作ったクラスローダは、名前からバイト列を取得する方法を知らないので、 リソースの解決が出来ないというわけです。 | ||||||||
|
投稿日時: 2006-12-12 05:03
下記のようにプログラムを大幅に変更してみました。
が、これだとエラーが出ます。 三行目でprivate final ByteArrayOutpuStream baos; という風に渡してみたのですが、mainのClassRoader = new Adapt(baos, className);で引っかかってしまいます。 当たり前だというご指摘を頂きそうですが・・・javaを始めてからまだ日が浅いのでご勘弁を。 方向性的にはよくなっているでしょうか?
| ||||||||
|
投稿日時: 2006-12-12 09:39
ちょっとコンパイルしてみましたが、
1) コンパイルエラーになりました。(loadClassのところはコメントで除いておいて) main関数の局所変数baosのスコープが変で、 ByteArrayOutputStream baos = new ByteArrayOutputStream(); を if(entry.getName().endsWith(".class")){ の前にしないとだめです。 2) Jarエントリはすべてクラスファイルだと思っているようですが、 ディレクトリエントリをクラスのように扱おうとしてloadClassで例外になります。 while((entry = (JarEntry) jis.getNextEntry()) != null){ String name = entry.getName(); の後に System.out.println("name=" + name); をいれて動かして見れば、まずディレクトリ名がでてくることがわかると思います。 したがってクラスを処理するのなら、 if (!name.endsWith(".class")) continue; を入れておけばよいと思います。 3) classNameが間違っています。 これも String className = name.replace("/", "\\.").replaceAll("\\.class", ""); のあとに System.out.println("className=" + className); をいれて動かしてみればすぐわかりますが、a/test.classがa\.testとなります。 String className = name.replace("\\", ".").replaceAll("\\.class", ""); とするべきでしょう。 | ||||||||
|
投稿日時: 2006-12-12 11:10
>だっちょさん
さっそくご指摘いただいた部分を直してみました。 ご指摘いただいて初めてはっと気づいて、恥ずかしく思います。 さて、その後loadClassの部分も含めコンパイルしてみたのですが、 今度は return defineClass(name, b, 0, b.lengh); の部分でエラーが出てしまいました。 これは、リターンしているものが違うのでしょうか? |
1|2|3|4|5
次のページへ»