Javaの Class.forName() メソッドを経由してJRubyで定義したclassを取得する方法は無いものか(追記あり)
JRuby経由でEsperを使ってるんだけど、そこにこんなコードがある。
String className = desc.getFunctionClassName(); Class clazz; try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); clazz = Class.forName(className, true, cl); } catch (ClassNotFoundException ex) { throw new EngineImportException("Could not load aggregation class by name '" + className + "'", ex); }
このコードで解決される clazz にどうにかしてJRubyで定義したclassを渡したいなーと思うんだけど、うまくいかない。どうすればいいんだ、という話。JRuby 1.7.4。
JRubyで定義するクラスのパッケージ名
JRuby上で定義したRuby classに java のパッケージ名をつけるには java_package というものを使う、らしい。
java_package 'is.tagomor.test' class Hoge # ... end
こうすると fully qualified class name としては is.tagomor.test.Hoge となる……らしいんだけど、これが本当に指定されてるかどうかよくわからないんだよな。
次のようなコードを実行してみると ClassNotFoundException になる。
require 'java' java_package 'is.tagomor.test' class Hoge; end loader = JRuby.runtime.jruby_class_loader java.lang.Class.forName('is.tagomor.test.Hoge', true, loader)
class定義部分だけ jar にしといたらいいのかなーと思ったけど、してみてもダメ。
$ cat hoge/hoge.rb require 'java' java_package 'is.tagomor.test' class Hoge; end $ jrubyc hoge/hoge.rb $ jruby -r hoge.jar x.rb $ LC_ALL=C jar -cfe hoge.jar hoge/hoge.class hoge $ cat x.rb require 'java' loader = JRuby.runtime.jruby_class_loader java.lang.Class.forName('is.tagomor.test.Hoge', true, loader) $ jruby -r hoge.jar x.rb URLClassLoader.java:202:in `run': java.lang.ClassNotFoundException: is.tagomor.test.Hoge from AccessController.java:-2:in `doPrivileged' from URLClassLoader.java:190:in `findClass' from JRubyClassLoader.java:92:in `findClass' from ClassLoader.java:306:in `loadClass' from ClassLoader.java:247:in `loadClass' from Class.java:-2:in `forName0' from Class.java:249:in `forName' (以下省略)
で
どうすればいいんだ。どなたかご存知ありませんか。
追記 at 8/8
親切な人にいろいろ教えてもらいました!
パスをがんばる
いったんjarに落とせる場合にはコメント欄に id:kimutansk さんにもらった方法でいいっぽい。つまりJavaっぽくパッケージ名に沿ったかたちでディレクトリ階層を掘り、そこに置いた .rb のファイルを .class にコンパイルして jar にまとめ、それを読み込む。
become_java! を引数つきで呼ぶ
require 'jruby/core_ext' した後で使える Class#become_java! メソッドがJavaクラスを作ってくれるとのことで試してみたけど、どうにもうまくいかない。
で、JVMが読み込める位置に .class ファイルをダンプしてやる必要があるとかなんとかで、つまり become_java! を引数つきで呼ばないといけないようだ。というか、そうするようにしたらうまくいった。 @yasushia さんに教えてもらいました。
require 'java' require 'jruby/core_ext' class Hoge; end klass = Hoge.become_java!(".") cl= java.lang.Thread.current_thread.getContextClassLoader p java.lang.Class.forName(klass.get_name, true, cl)
このあたりを見るとパスじゃなくて適切なクラスローダのインスタンスを与えてもいいっぽいけど、Esperの中から該当のインスタンスをひっぱり出してくる方法がアレなので今のところ未確認。
https://github.com/jruby/jruby/blob/master/lib/ruby/shared/jruby/core_ext/class.rb#L50..L60