System#exitを呼び出してもVMを終了させない方法
こんな感じででけた。もしこのコードを試すのであれば、ラーニングテストなので、適宜org.junit.Testをインポートしてね。
@Test public void exitしないことを確認しよう() throws Exception { System.setSecurityManager(new SecurityManager() { @Override public void checkExit(int status) { throw new SecurityException(); } @Override public void checkPermission(final Permission perm) { } @Override public void checkPermission(final Permission perm, final Object context) { } }); try { // はーい、ここ注目ですよー。 System.exit(0); } catch (Exception e) { } System.out.println("System#exitを実行したのに終了しなかったYO!"); }
実行してみると、こんな感じでコンソールに表示されるはず。
System#exitを実行したのに終了しなかったYO! Exception in thread "main" java.lang.SecurityException at learning.Learning$1.checkExit(Learning.java:17) at java.lang.Runtime.exit(Runtime.java:88) at java.lang.System.exit(System.java:921) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:202)
SecurityExceptionが後に表示されるのは、なぜなんでしょう?そこは分かってないです。なんでこんな事を試したか、というと、アプリケーションの中にスクリプトコードでカスタマイズできる部分を用意し、ユーザに自由にカスタマイズしてもらう事を想定すると、System#exitを実行されてVMが終了されるのは非常に困るからです。実際、JAMCircleのスクリプトコンソールにこんな感じで組み込んでみると、JRubyのコードからSystem.exitを実行しても終了されないようになりました。
SecurityManager securityManager = System.getSecurityManager(); System.setSecurityManager(new SecurityManager() { @Override public void checkExit(int status) { throw new SecurityException(); } @Override public void checkPermission(final Permission perm) { } @Override public void checkPermission(final Permission perm, final Object context) { } }); // run ruby code runtimeAdapter.eval(runtime, script); // retrieve security manager System.setSecurityManager(securityManager);
そういうのはpolicyに書くといいよ、というお話もありますが、JAMCircleみたいな単体アプリケーションを配布する事を考えると、ユーザにpolicyを修正して頂く事になるので現実的ではありません。本当はJRuby等のスクリプトコンテナの方で設定できるのがスクリプトコンテナの利用者からするといいと思うんです。なんとなくアプリケーションの責務ってよりも、スクリプトエンジンの責務っぽいですから。ただ、これをこのまま組み込んでしまうと、スクリプトから任意のファイルを触れると思うので、それもセキュリティ上よろしくないですよね。実際に組み込むのはもう少し考える事がありそうです。
追記
コメントでも頂きましたが、上記のコンソールに表示されるスタックとレースは、RemoteTestRunner内でSystem#exitが呼ばれているからでした。
2010/08/13追記
スクリプト実行中に他の操作で終了処理するとプロセスが落ちませんね。ちゃんと呼び出し元を判別する必要があるみたい。
本腰入れてJRuby(その3) Apache BSFさんと仲良くなろう
BSFのドキュメントがほとんどなくて困ってうろうろしたので、分かった事を書いていく。
BSFを使ったスクリプトの呼び出しはこんな感じ。
BSFManager manager = new BSFManager(); String hello = "Javaの世界からこんにちは!"; manager.registerBean("hello", hello); // (1) InputStreamReader reader = new InputStreamReader(this.getClass().getResource("test.rb").openStream()); manager.exec("ruby", "(java)", -1, -1, IOUtils.getStringFromReader(reader));
test.rbは、この処理を実装しているクラスと同じパッケージに置いている事を前提にしています。(ってあたりまえかw)
test.rbの中身はこんな感じ。
hello = $bsf.lookupBean("hello") puts hello
$bsfというのは、BSFManagerをラップしたBSFFunctionsというクラスの暗黙オブジェクト。BSFManager#registerBeanで登録したBeanをlookupBeanで取得することができます。
BSFManagerにはもう一つ、BSFManager#declareBean(String beanName,Object bean,Class beanClass)というインターフェースがあり、こちらを使うと宣言したbeanはさっきの$bsfからlookupBeanしなくてもそのまま使えます。どういうことかと言うと、
String hello = "Javaの世界からこんにちは!"; // manager.registerBean("hello", hello); // (1) manager.declareBean("hello", hello,String.class); // (1)をこう置き換える
puts $hello
と言うように呼べます。が、BSFManagerのソースを見てみると、すべてのScriptEngineに対してdeclareBeanで宣言したBeanが登録されていました。局所的に使うBeanを登録するにはちょっとスコープが広すぎるわけです。(Rubyの世界だと'$'がついている変数はグローバルスコープなんすね。Rhinoだと"bsf"のまんまで呼び出せた。他の言語だとどうなんだろう。)
BSFの対応言語はJRuby以外にもJavaScript(Rhino)やJython、Groovyとかがあり、実行時にどの言語かを指定することができます。(Scalaがなくてビックリした)
String source; // JRuby manager.exec("ruby", "(java)", -1, -1,source); // Rhino manager.exec("javascript", "(java)", -1, -1,source); // Jython manager.exec("jython", "(java)", -1, -1,source); // Groovy manager.exec("groovy", "(java)", -1, -1,source);
BSFManagerと同じパッケージにあるLanguage.propertiesでScriptEngineとマッピングしているようです。またファイルの拡張子からどの言語か判断して実行するインターフェースもあります。
String langType = BSFManager.getLangFromFilename(filename);
Java6でサポートされたJSR233のインターフェースの方が実行速度的に優位ですが、Java5でも動く環境にするのならBSFを使わないといけません。JRubyのインターフェースを直で叩く事も考えられますが、JRubyの開発者は推奨していません。(そりゃそうだ。)
JRuby on Eclipse RCP
だってさ。いや、やってみたらおもしろそうだと思うんだけど、ほんとにやった強者っているもんだね。http://wiki.jruby.org/wiki/JRuby_on_Eclipse_RCP
本腰入れてJRubyを使いたくなってきた。
jruby-complete.jarはrubyのライブラリ一式を含んでいる。mavan-repositoryにも置いてあるけど、JRubyのソースを落としてきて
ant jar-complete
rubygemsによる拡張ができるかどうか、までは調べてないけれど、たぶん環境変数とか、システムプロパティでどうにかできるレベルだと思われる。
それよりも、Rubyのライブラリが同梱されていることの心強さ。これはいい。Javaでこれだけのライブラリを、こんなフットプリントではなかなか手に入らない気がする。Javaってどれだけライブラリのせいで太らされるのか、なんか分からされた。
で、Java5ベースで行きたいのでBSFのお世話になってますが、BSFはJRubyに添付されているやつはちょっと手が加えられているらしいので、Apacheから最新版を落とした方がいいらしい。