JRuby使用经验

来源:互联网 发布:单片机仿真软件有哪些 编辑:程序博客网 时间:2024/05/24 06:37

分类: 动态语言-ruby 241人阅读 评论(0) 收藏 举报

首先我是一个Java程序员,很喜欢Ruby.

公司由于业务的需要,在Java项目中引入动态语言,目的是可以快速地修改业务逻辑以响应快速变化的业务需求.于是我有幸当了一回JRuby的先锋.当初使用JRuby的时候,我对JRuby项目的了解其实就是知道它可以让Ruby运行在JVM上面,其余细节一概不知,都是在实际使用中一点点地摸索,一点点地积累回来.

 

在这一过程中,在 dennis_zane 同学身上,我学到了很多与Ruby相关或者不相关的东西,借机感谢一下.

JRuby的中文资料相当的稀少,在 Google上搜索,来来去去的就是介绍了下最基本的怎么从Java中调用Ruby代码,或者在Ruby中使用Java的类库.我从无数次遇到问题 => 解决问题的循环中也有那么一点点的使用心得,记录之,备忘.

    * JRuby的入门资料,请访问 JRuby wiki 一般的使用方法这里都有介绍.
    * 有两种方法可以使用JRuby,一是用BSF,二是使用JDK 6.BSF的方式已经过时了,JDK6中内置了对脚本语言的支持,默认的Javascript,要使用JRuby还要下载juby-engine.jar,当前最新版本是1.1.6 地址:https://scripting.dev.java.net/files/documents/4957/115972/jruby-engine-1.1.6.zip 

=======================================华丽的分割线========================================
如果使用jar打包,在ruby代码中调用java的类

 

Ruby代码 复制代码
  1. require "your_jar_file_name.jar"  
  2. import your_packet_name   
[Ruby] view plaincopy
  1. require "your_jar_file_name.jar"  
  2. import your_packet_name  



java 方法:

 

Java代码 复制代码
  1. class JavaClazz {   
  2.     public void javaMethod(int i) {   
  3.         System.out.pintln(i);   
  4.     }   
  5. }   
[Java] view plaincopy
  1. class JavaClazz {  
  2.     public void javaMethod(int i) {  
  3.         System.out.pintln(i);  
  4.     }  
  5. }  


在Ruby中如是调用:

 

Ruby代码 复制代码
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(1)   
[Ruby] view plaincopy
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(1)  


将会抛出类型不匹配的异常,因为所有ruby中的数值,传递到java那里都是 Long 类型,解决办法如下:

 

Ruby代码 复制代码
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(java.lang.Integer.new(1))   
[Ruby] view plaincopy
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(java.lang.Integer.new(1))  

注:以上代码是运行在 JRuby 1.1.2 版本下,在最新版本 1.2.0中已经没有这个问题了, 多谢 RednaxelaFX 同学的指正.
=======================================华丽的分割线========================================
如果在java中使用了可变参数:

 

Java代码 复制代码
  1. class JavaClazz {   
  2.     public void javaMethod(int i,String... s) {   
  3.         ... // your code   
  4.     }   
  5. }   
[Java] view plaincopy
  1. class JavaClazz {  
  2.     public void javaMethod(int i,String... s) {  
  3.         ... // your code  
  4.     }  
  5. }  


在ruby中应该这样调用:

 

Ruby代码 复制代码
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(java.lang.Integer.new(1),'this is a string')   
  3. // 只有一个参数,如果你知道java中的可变参数其实是一个数组的话   
  4.  java_clazz.javaMethod(java.lang.Integer.new(1),[].to_java(java.lang.String))   
[Ruby] view plaincopy
  1. java_clazz = JavaClazz.new  
  2. java_clazz.javaMethod(java.lang.Integer.new(1),'this is a string')  
  3. // 只有一个参数,如果你知道java中的可变参数其实是一个数组的话  
  4.  java_clazz.javaMethod(java.lang.Integer.new(1),[].to_java(java.lang.String))  

 

=======================================华丽的分割线========================================

调用java中的常量,枚举enum

 

Java代码 复制代码
  1. class JavaClazz {   
  2.     public final String CONSTANT = "I can not change!"  
  3.     public enum Season { winter, spring, summer, fall }   
  4. }   
[Java] view plaincopy
  1. class JavaClazz {  
  2.     public final String CONSTANT = "I can not change!"  
  3.     public enum Season { winter, spring, summer, fall }  
  4. }  

 

 

Ruby代码 复制代码
  1. puts JavaClazz::CONSTANT   
  2. puts JavaClazz::Season.winter   
[Ruby] view plaincopy
  1. puts JavaClazz::CONSTANT  
  2. puts JavaClazz::Season.winter  


=======================================华丽的分割线========================================


如果你想使用ruby核心包,必须正确设置jruby的加载路径,从Sun实现的JRubyScriptEngine.java的源代码可以看到:

Java代码 复制代码
  1. //加载核心包的路径就是放在这个系统属性中的   
  2. System.getProperty("com.sun.script.jruby.loadpath");   
  3. //可以设置自己的路径   
  4. System.setProperty("com.sun.script.jruby.loadpath","/root/.jruby/lib/ruby/1.8")  
[Java] view plaincopy
  1. //加载核心包的路径就是放在这个系统属性中的  
  2. System.getProperty("com.sun.script.jruby.loadpath");  
  3. //可以设置自己的路径  
  4. System.setProperty("com.sun.script.jruby.loadpath","/root/.jruby/lib/ruby/1.8")  


=======================================华丽的分割线========================================
关于官方JRuby引擎的问题
Sun官方实现的脚本引擎在多并发的情况下是会比较慢的,查看JRubyScriptEngine.java的源代码,可以看到eval方法是加上了synchronized

 

Java代码 复制代码
  1. public synchronized Object eval(Reader reader, ScriptContext ctx)   
  2.                throws ScriptException {   
  3.     Node node = compileScript(reader, ctx);   
  4.     return evalNode(node, ctx);   
  5. }   
[Java] view plaincopy
  1. public synchronized Object eval(Reader reader, ScriptContext ctx)  
  2.                throws ScriptException {  
  3.     Node node = compileScript(reader, ctx);  
  4.     return evalNode(node, ctx);  
  5. }  


我至今想不明白,这个官方实现为什么会加上 synchronized

我自己山寨了一个JRubyScriptEngine的东西,直接调用JRuby的 JavaEmbedUtils 类来执行脚本,还是相当好用的.

Java代码 复制代码
  1. /**  
  2.  *  
  3.  */  
  4. package org.opensource.script.jruby;   
  5.   
  6. import java.io.File;   
  7. import java.io.FileInputStream;   
  8. import java.io.FileNotFoundException;   
  9. import java.io.InputStream;   
  10. import java.util.ArrayList;   
  11. import java.util.HashMap;   
  12. import java.util.Map;   
  13.   
  14. import org.jruby.Ruby;   
  15. import org.jruby.RubyRuntimeAdapter;   
  16. import org.jruby.javasupport.JavaEmbedUtils;   
  17. import org.jruby.runtime.GlobalVariable;   
  18. import org.jruby.runtime.builtin.IRubyObject;   
  19.   
  20. /**  
  21.  * 山寨版JRubyScriptEngine  
  22.  * 弃用官方版的原因是由于它封装的invokeMethod方法使用了synchronized关键字,在多并发的情况下性能极差. 
  23.  *  
  24.  * @author yanghuan  
  25.  *  
  26.  */  
  27. public class JRubyScriptEngine {   
  28.     private final Ruby runtime;   
  29.     private final RubyRuntimeAdapter evaler;   
  30.     private final Map<String, IRubyObject> rubyObjectCache = new HashMap<String, IRubyObject>();   
  31.   
  32.     public JRubyScriptEngine() {   
  33.         ArrayList<String> loadPaths = new ArrayList<String>();   
  34.         loadPaths.add("/root/.jruby/lib/ruby/1.8");   
  35.         loadPaths.add("/root/.jruby/lib/ruby/site_ruby/1.8");   
  36.         runtime = JavaEmbedUtils.initialize(loadPaths, JavaEmbedUtils   
  37.                 .createClassCache(this.getClass().getClassLoader()));   
  38.         evaler = JavaEmbedUtils.newRuntimeAdapter();   
  39.     }   
  40.   
  41.     /**  
  42.      * 根据脚本的路径,获取脚本eval后的Ruby对象,首先会从cache中检索,如有即时返回,如果没有则eval脚本,再返回.保证不会重复eval  
  43.      *  
  44.      * @param fullPath  
  45.      *            绝对路径  
  46.      * @return  
  47.      * @throws FileNotFoundException  
  48.      * @throws Exception  
  49.      */  
  50.     private IRubyObject getRubyObject(final String fullPath) throws FileNotFoundException {   
  51.         if (rubyObjectCache.get(fullPath) == null) {   
  52.             return evalScript(fullPath);   
  53.         }   
  54.         return rubyObjectCache.get(fullPath);   
  55.     }   
  56.   
  57.     /**  
  58.      * 执行脚本,返回脚本对象 把这个方法从  
  59.      * #getRubyObject分出来,并且加上synchronized关键字,纯粹是为了防止多并发重复eval脚本  
  60.      *  
  61.      * @param fullPath  
  62.      * @return  
  63.      * @throws FileNotFoundException  
  64.      */  
  65.     private synchronized IRubyObject evalScript(final String fullPath) throws FileNotFoundException {   
  66.         if (rubyObjectCache.get(fullPath) == null) {   
  67.             File scriptFile = new File(fullPath);   
  68.             InputStream in = new FileInputStream(scriptFile);   
  69.             IRubyObject rubyObject = evaler.parse(runtime, in, scriptFile.getAbsolutePath(), 1).run();   
  70.             rubyObjectCache.put(fullPath, rubyObject);   
  71.             return rubyObject;   
  72.         }   
  73.         return rubyObjectCache.get(fullPath);   
  74.     }   
  75.   
  76.     /**  
  77.      * 加载脚本  
  78.      *  
  79.      * @param fullPath  
  80.      * @throws FileNotFoundException  
  81.      */  
  82.     public void load(final String fullPath) throws FileNotFoundException {   
  83.         getRubyObject(fullPath);   
  84.     }   
  85.   
  86.     /**  
  87.      * 清空已加载脚本对象  
  88.      *  
  89.      * @param fullPath  
  90.      */  
  91.     public void clean(final String fullPath) {   
  92.         if (rubyObjectCache.get(fullPath) != null) {   
  93.             rubyObjectCache.remove(fullPath);   
  94.         }   
  95.     }   
  96.   
  97.     /**  
  98.      * 定义全局变量  
  99.      *  
  100.      * @param name  
  101.      *            变量名,不用以$开头  
  102.      * @param value  
  103.      *            值  
  104.      */  
  105.     public void defineGlobalVariable(final String name, final Object value) {   
  106.         IRubyObject rubyObject = JavaEmbedUtils.javaToRuby(runtime, value);   
  107.         /**  
  108.          * 这个全局变量的定义有点儿诡异,源代码是这样定义的:globalVariables.define(variable.name(),  
  109.          * newIAccessor() {}),所以必须手工加上 $ 开关  
  110.          **/  
  111.         GlobalVariable variable = new GlobalVariable(runtime, name.startsWith("$") ? name : "$" + name, rubyObject);   
  112.         runtime.defineVariable(variable);   
  113.     }   
  114.   
  115.     /**  
  116.      * 执行脚本中定义的class的方法  
  117.      *  
  118.      * @param fullPath  
  119.      *            脚本绝对路径  
  120.      * @param method  
  121.      *            方法名  
  122.      * @param args  
  123.      *            参数  
  124.      * @return  
  125.      * @throws FileNotFoundException  
  126.      */  
  127.     public Object invokeMethod(final String fullPath, final String method, Object[] args) throws FileNotFoundException {   
  128.         IRubyObject rubyObject = getRubyObject(fullPath);   
  129.         return JavaEmbedUtils.invokeMethod(runtime, rubyObject, method, args, Object.class);   
  130.     }   
  131. }  
[Java] view plaincopy
  1. /** 
  2.  * 
  3.  */  
  4. package org.opensource.script.jruby;  
  5.   
  6. import java.io.File;  
  7. import java.io.FileInputStream;  
  8. import java.io.FileNotFoundException;  
  9. import java.io.InputStream;  
  10. import java.util.ArrayList;  
  11. import java.util.HashMap;  
  12. import java.util.Map;  
  13.   
  14. import org.jruby.Ruby;  
  15. import org.jruby.RubyRuntimeAdapter;  
  16. import org.jruby.javasupport.JavaEmbedUtils;  
  17. import org.jruby.runtime.GlobalVariable;  
  18. import org.jruby.runtime.builtin.IRubyObject;  
  19.   
  20. /** 
  21.  * 山寨版JRubyScriptEngine 
  22.  * 弃用官方版的原因是由于它封装的invokeMethod方法使用了synchronized关键字,在多并发的情况下性能极差. 
  23.  * 
  24.  * @author yanghuan 
  25.  * 
  26.  */  
  27. public class JRubyScriptEngine {  
  28.     private final Ruby runtime;  
  29.     private final RubyRuntimeAdapter evaler;  
  30.     private final Map<String, IRubyObject> rubyObjectCache = new HashMap<String, IRubyObject>();  
  31.   
  32.     public JRubyScriptEngine() {  
  33.         ArrayList<String> loadPaths = new ArrayList<String>();  
  34.         loadPaths.add("/root/.jruby/lib/ruby/1.8");  
  35.         loadPaths.add("/root/.jruby/lib/ruby/site_ruby/1.8");  
  36.         runtime = JavaEmbedUtils.initialize(loadPaths, JavaEmbedUtils  
  37.                 .createClassCache(this.getClass().getClassLoader()));  
  38.         evaler = JavaEmbedUtils.newRuntimeAdapter();  
  39.     }  
  40.   
  41.     /** 
  42.      * 根据脚本的路径,获取脚本eval后的Ruby对象,首先会从cache中检索,如有即时返回,如果没有则eval脚本,再返回.保证不会重复eval 
  43.      * 
  44.      * @param fullPath 
  45.      *            绝对路径 
  46.      * @return 
  47.      * @throws FileNotFoundException 
  48.      * @throws Exception 
  49.      */  
  50.     private IRubyObject getRubyObject(final String fullPath) throws FileNotFoundException {  
  51.         if (rubyObjectCache.get(fullPath) == null) {  
  52.             return evalScript(fullPath);  
  53.         }  
  54.         return rubyObjectCache.get(fullPath);  
  55.     }  
  56.   
  57.     /** 
  58.      * 执行脚本,返回脚本对象 把这个方法从 
  59.      * #getRubyObject分出来,并且加上synchronized关键字,纯粹是为了防止多并发重复eval脚本 
  60.      * 
  61.      * @param fullPath 
  62.      * @return 
  63.      * @throws FileNotFoundException 
  64.      */  
  65.     private synchronized IRubyObject evalScript(final String fullPath) throws FileNotFoundException {  
  66.         if (rubyObjectCache.get(fullPath) == null) {  
  67.             File scriptFile = new File(fullPath);  
  68.             InputStream in = new FileInputStream(scriptFile);  
  69.             IRubyObject rubyObject = evaler.parse(runtime, in, scriptFile.getAbsolutePath(), 1).run();  
  70.             rubyObjectCache.put(fullPath, rubyObject);  
  71.             return rubyObject;  
  72.         }  
  73.         return rubyObjectCache.get(fullPath);  
  74.     }  
  75.   
  76.     /** 
  77.      * 加载脚本 
  78.      * 
  79.      * @param fullPath 
  80.      * @throws FileNotFoundException 
  81.      */  
  82.     public void load(final String fullPath) throws FileNotFoundException {  
  83.         getRubyObject(fullPath);  
  84.     }  
  85.   
  86.     /** 
  87.      * 清空已加载脚本对象 
  88.      * 
  89.      * @param fullPath 
  90.      */  
  91.     public void clean(final String fullPath) {  
  92.         if (rubyObjectCache.get(fullPath) != null) {  
  93.             rubyObjectCache.remove(fullPath);  
  94.         }  
  95.     }  
  96.   
  97.     /** 
  98.      * 定义全局变量 
  99.      * 
  100.      * @param name 
  101.      *            变量名,不用以$开头 
  102.      * @param value 
  103.      *            值 
  104.      */  
  105.     public void defineGlobalVariable(final String name, final Object value) {  
  106.         IRubyObject rubyObject = JavaEmbedUtils.javaToRuby(runtime, value);  
  107.         /** 
  108.          * 这个全局变量的定义有点儿诡异,源代码是这样定义的:globalVariables.define(variable.name(), 
  109.          * newIAccessor() {}),所以必须手工加上 $ 开关 
  110.          **/  
  111.         GlobalVariable variable = new GlobalVariable(runtime, name.startsWith("$") ? name : "$" + name, rubyObject);  
  112.         runtime.defineVariable(variable);  
  113.     }  
  114.   
  115.     /** 
  116.      * 执行脚本中定义的class的方法 
  117.      * 
  118.      * @param fullPath 
  119.      *            脚本绝对路径 
  120.      * @param method 
  121.      *            方法名 
  122.      * @param args 
  123.      *            参数 
  124.      * @return 
  125.      * @throws FileNotFoundException 
  126.      */  
  127.     public Object invokeMethod(final String fullPath, final String method, Object[] args) throws FileNotFoundException {  
  128.         IRubyObject rubyObject = getRubyObject(fullPath);  
  129.         return JavaEmbedUtils.invokeMethod(runtime, rubyObject, method, args, Object.class);  
  130.     }  
  131. }  
原创粉丝点击