JRuby #02 - Ruby rozmawia z Javą, Java rozmawia z Ruby’im
Widzę, że jesteś pierwszy raz na moim blogu. Witaj :). Może nawet będziesz miał ochotę subskrybować blog poprzez RSS. Trzymaj się ciepło!
W poprzednim poście opisywałem proces instalacji JRuby’iego tak, żeby korzystanie z niego było proste. Czas wziąć na warsztat integrację Ruby’iego z Javą i odwrotnie.
Ruby rozumie Javę
Kiedy JRuby interpretuje skrypt Ruby’iego to mogą tam być używane wszystkie klasy Javowe. Chcemy, żeby skrypt korzystał z naszej wcześniej utworzonej biblioteki w Javie? Nie ma problemu, dołączamy ją do CLASSPATH i możemy z poziomu Ruby’iego z niej korzystać. Integracja została przedstawiona na poniższych przykładach. Dołożyłem wszelkich starań, aby komentarze były wyczerpujące.
Uruchommy interakcyjną konsolę JRuby’iego: jruby -S jirb i następnie pobawmy się kodem:
# Znane z Javy importowanie wygląda standardowo. # Niestety importowanie z gwiazdką nie działa, więc import javax.swing.* poczęstuje nas błędem. import javax.swing.JFrame import javax.swing.JButton # Składnia Ruby'iego została zachowana dla obiektów Javowych, widać to w tworzeniu nowego egzemplarza JFrame # Warto zauważyć, że nawiasy są nieobowiązkowe. Poniższe wywołanie jest równoznaczne z JFrame.new("...") frame = JFrame.new "Ruby swinguje z Java" # Styl kodowania Ruby'iego zostaje zachowany także dla metod. W Javie lubimyTakPisać, a w Ruby'im trochę_inaczej. # Poniższe wywołanie jest równoważne z frame.setSize(300, 300), które także byłoby poprawne. frame.set_size 300, 300 # Gettery i settery Javowe są dostępne także w odmianie Ruby'iego. # Poniższa linijka jest równoważna z frame.setAlwaysOnTop(true), które także byłoby poprawne. frame.always_on_top = true button = JButton.new "Nacisnij mnie" # Blok kodu zostanie w locie przekształcony w klasę implementującą interfejs ActionListener. Niezłe! # Jest to domyślne zachowanie JRuby'iego, jeśli parametrem jest interfejs z jedną metodą. # Standardowo w Ruby'im używa się wcięć z 2 spacjami. button.add_action_listener do |event| # Przykład getterów ala Ruby. Równoznaczne z event.getSource().setText("..."), które także jest OK. event.source.text = "Nie naciskaj mnie ponownie!" event.source.enabled = false end frame.add(button) frame.show |
Czas poczęstować Ciebie czytelniku jakimś smakołykiem. Oto on:
# Rozszerzenie klasy Javowej String o mechanizm missing_method, czyli # ta metoda zostanie wywołana, jeśli na obiekcie zostanie wywołana metoda, która # nie została zdeklarowana. JavaUtilities.extend_proxy "java.lang.String" do def method_missing(symbol, *args) puts "Kogo wolasz?" end end txt = java.lang.String.new "Ala ma kota" txt.hmmmm_jak_brzmiala_nazwa_tej_metody?() # => "Kogo wolasz?" |
Java rozumie Ruby’iego
Ruby może zostać wpleciony w kod Javy na 3 sposoby:
- poprzez Scripting API (wprowadzone z Javą 6, JSR 223, unifikacja silników skryptowych używanych w Javie), szczegóły: https://scripting.dev.java.net,
- poprzez Beans Scripting Framework (standard wprowadzany przez Apache Jakarta), szczegóły: http://jakarta.apache.org/bsf,
- bezpośrednio odwołując się do interpretera JRuby’iego.
Zajmiemy się tylko pierwszą opcją. Będziemy do tego potrzebowali opakowania silnika JRuby’iego dla javax.script, które znajdziemy tutaj (najlepiej wersję 1.1.3).

Powyższa grafika przedstawia zależności w Java Scripting API. Z naszego kodu będziemy odwoływać się wyłącznie do klas javax.script.*, żądać silnika JRuby’iego (jruby-engine.jar), który z kolei sam już będzie odpowiednio wywoływał interpreter JRuby’iego.
Czas podwinąć rękawy i trochę pokodować.
import org.jruby.RubyHash; import javax.script.*; import java.util.List; import java.util.ArrayList; import java.util.Map; import static java.lang.System.*; public class RubyInJava { public static void main(String[] args) { // Od Java Scripting API żądamy silnik JRuby'iego ScriptEngine rubyEngine = new ScriptEngineManager().getEngineByName("jruby"); ScriptContext context = rubyEngine.getContext(); List<integer> list = new ArrayList<integer>() {{ add(1); add(2); add(3); add(4); }}; // Do środowiska JRuby'iego dodajemy globalną zmienną list rubyEngine.put("list", list); // Skrypt Ruby'iego, który będziemy wykonywać StringBuilder script = new StringBuilder(); script.append("puts '--- Ruby ---' ").append("\n") .append("puts $list.inspect ").append("\n") // JRuby do kolekcji Javowych dodaje typowe metody Ruby'iego dla kolekcji .append(" $list.each { |item| ") .append(" puts \"Element #{item}\" ") .append(" } ").append("\n") .append(" puts \"Suma: #{$list.inject { |sum, i| sum + i }}\" ").append("\n") .append("\n") .append(" $hash = { :ala => :ma, :co => :kota } ").append("\n"); // Wykonanie skryptu try { rubyEngine.eval(script.toString(), context); } catch (ScriptException e) { e.printStackTrace(); return; } out.println("--- Java ---"); // Pobranie zmiennej globalnej po wykonaniu skryptu Object hashAsObject = rubyEngine.get("hash"); out.println("Klasa: " + hashAsObject.getClass()); RubyHash hash = (RubyHash) hashAsObject; for (Object o : hash.entrySet()) { Map.Entry entry = (Map.Entry) o; System.out.println(entry.getKey() + " => " + entry.getValue()); } } } |
Aby uruchomić powyższy kawałek kodu trzeba mieć w CLASSPATH: jruby.jar oraz jruby-engine.jar.
Powyższe przykłady ilustrują, że ludzie pracujący nad JRuby’im wykonali kawał dobrej roboty, żeby łączenie Javy z Ruby’im było bezstresowe dla developerów. Takie połączenie dwóch światów daje nam nowe możliwości: tworzenie mini-języków dla aplikacji Javowych, aplikacje Swingowe w Ruby’im, nie wspominając o Ruby on Rails, a dla Ruby’istów dostępny jest cały ekosystem Javy :).

Nazywam się
Wiktor Gworek
i jestem gospodarzem tego bloga.
niezłe :] czekam na #3
Faktycznie ciekawe
a do czego tego się używa w świecie? jakie są tego silne strony?
A ja mam problem, z którym walczę cały dzień.
No i niestety google nie chce mi zbyt pomóc.
Czy wiesz może czym może być to spowodowane?
Linia 62 to ScriptEngine jsEngine = mgr.getEngineByName(”jruby”);