現実世界のjruby(ショートバージョン)
DESCRIPTION
JavaOne Tokyo 2012 JVM言語BOF JRuby発表TRANSCRIPT
現実世界のJRuby
(ショートバージョン)
日本JRubyユーザ会 中村浩士@nahi [email protected]://github.com/nahi
ロングバージョン: http://bit.ly/RealWorldJRubyJa
自己紹介
ネットワークセキュリティ関連のシステム開発
C/C++ (18年)、Java (13年)、Ruby (13年) 余暇のOSS開発
CRuby (8年) とJRuby (2年) のコミッタsoap4r、httpclient他の開発
JRubyとは - http://jruby.org/
最新リリース版は1.6.7 JVM上で動作するRuby(動的型言語)
Open Source (CPL, GPL, LGPL) 開発開始から10年
日本でのJRuby
@yokolet @nahi @koichirooJRubyコミッタ3人
日本JRubyユーザ会: http://bit.ly/JRubyUsersJp昨年度の勉強会実施実績: 0回
本日のゴール
Java開発者、他のJVM言語利用者向け
RubyとJRubyについて学ぶ
JRubyはどんなところで使われている?
Rubyの特徴
人に優しい文法 豊富なメタプログラミング機能 高い生産性 Ruby on Rails + githubの存在
Rubyツアー 1/8: クラス定義
public class Circle extends Shape { private final int radius; public Circle(int radius) { this.radius = radius; } public int getRadius() { return radius; } public double getArea() { return Math.PI * Math.pow(radius, 2); } public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); }}
extends → <継承は単一継承
メソッド定義 → defコンストラクタ → initialize
class Circle < Shape def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) endendputs Circle.new(2).area
public class Circle extends Shape { private final int radius; public Circle(int radius) { this.radius = radius; } public int getRadius() { return radius; } public double getArea() { return Math.PI * Math.pow(radius, 2); } public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); }}
class Circle < Shape def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) endendputs Circle.new(2).area
Rubyツアー 2/8: インスタンス変数
this → @
attr_readerはアクセサメソッド定義用メソッド
public class Circle extends Shape { private final int radius; public Circle(int radius) { this.radius = radius; } public int getRadius() { return radius; } public double getArea() { return Math.PI * Math.pow(radius, 2); } public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); }}
class Circle < Shape def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) endendputs Circle.new(2).area
Rubyツアー 3/8: 動的型付け
変数に型なしduck-typing
引数の型・数の違いによるメソッドoverloadなし
public class Circle extends Shape { private final int radius; public Circle(int radius) { this.radius = radius; } public int getRadius() { return radius; } public double getArea() { return Math.PI * Math.pow(radius, 2); } public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); }}
class Circle < Shape def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) endendputs Circle.new(2).area
Rubyツアー 4/8: 全てが値を持つ
return不要文の値は最後の式
public class Circle extends Shape { private final int radius; public Circle(int radius) { this.radius = radius; } public int getRadius() { return radius; } public double getArea() { return Math.PI * Math.pow(radius, 2); } public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); }}
class Circle < Shape def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) endendputs Circle.new(2).area
Rubyツアー 5/8:全てがオブジェクト、全てがメソッド
Circle: 定数a*2 == a.*(2)
Circle.new: クラスオブジェクトのnewメソッドを呼び出す
Rubyツアー 6/8: ブロック(クロージャ)
def aaa(name, &block) File.open(name) do |file| file.each_line do |line| yield line end endend
aaa('a.txt') do |line| p lineend
(1) File.open用ブロックブロック実行後に自動close (2) each_line用ブロック1行読み込む毎に呼ばれる (3) aaa用ブロックaaa内部のyieldに呼ばれる ← その他利用例
people.group_by { |e| e.lang }
button1 = ...label1 = ...button1.on_action do |event| label1.text = 'sending...'end
(1)(2)
(3)
Rubyツアー 7/8:Mix-in、オープンクラス
module Utils def name self.class.name endendclass Book include Utils def say "Hello from #{name}" endendobj = Book.newp obj.say #=> "Hello from Book"
class Book def say "I'm #{name}" endendp obj.say #=> "I'm Book"
Mix-in: 実装の継承Utilsモジュールの実装を
BookクラスにMix-in オープンクラス:Bookクラスのsayメソッドを再定義
実装
継承
Rubyツアー 8/8: フックメソッド
class Base @@all = [] def self.inherited(klass) @@all << klass endend
class Sub < Base p @@allend
class SubSub < Sub p @@allend
inherited: クラスが継承された場合に、継承したクラスを引数に呼ばれる その他: included、method_added、method_removed、method_missing、const_missing等※@@はクラス変数の接頭辞
※クラスオブジェクトのキャッシュは リークの元なので普通やらない
Ruby言語の特徴
動的型付け(Groovyと同様)
オブジェクト指向: 全てオブジェクト、全てメソッド ブロック(クロージャ)の活用 メタプログラミング支援
Mix-in、オープンクラス、各種フックメソッド
JRubyの特長
Ruby on Railsを含む100%の互換性
C言語版Rubyと同等の実行速度
高いスケーラビリティ(並行動作) Javaとの親和性の高さ
Real-World JRuby: JRuby利用実例
Java連携 (Java -> Ruby) Java連携 (Ruby -> Java) Javaテスト (RSpec, JtestR) 開発支援 (Ant, Maven, Jenkins) ウェブ開発 (JRuby on Rails)
Java連携 (Java -> Ruby)
JavaからRubyライブラリを利用
例: 独自定義ファイル解析の
DSL処理系として
import org.jruby.embed.ScriptingContainer;public class HelloWorld { public static void main(String[] args) { ScriptingContainer ruby = new ScriptingContainer(); ruby.runScriptlet("puts \"hello,world!\""); }}
source 'http://localhost/'
group :development do host 'localhost' port 12345 reloadable true debug trueend
group :production do host 'www.example.com'end
例: gitdiff.rb - gitライブラリを利用し、リビジョ
ンの変更サマリを取得するRubyコード
Java連携 (Java -> Ruby)
require 'rubygems'require 'git'def diff_summary(dir, from, to) diff = Git.open(dir).diff(from, to) diff.stats[:files].map { |file, st| insertions = st[:insertions] || 0 deletions = st[:deletions] || 0 "#{file} +#{insertions} -#{deletions}" }end# =>[ "src/org/jruby/Ruby.java +32 -20",# "src/org/jruby/RubyArray.java +93 -17",# "src/org/jruby/RubyBasicObject.java +7 -0", ...
Javaからの呼び出しと抽出
Java連携 (Java -> Ruby)
public class GitDiff { public static void main(String[] args) throws Exception {
ScriptingContainer ruby = new ScriptingContainer();ruby.runScriptlet("require 'gitdiff'");
ruby.put("dir", "/home/nahi/git/jruby/");ruby.put("from", "8c6dba0f...");ruby.put("to", "7837c84a...");
List array = (List) ruby.runScriptlet( "diff_summary(dir, from, to)");for (Object obj : array) {
System.out.println(obj.toString());}...
Java連携 (Ruby -> Java)
RubyからJavaの機能を利用する
Javaの対話環境としての利用も可能% jruby -S irb> require 'java'=> true> ni = java.net.NetworkInterface.networkInterfaces.to_a.first=> #<Java::JavaNet::NetworkInterface:0x4d33b92c>> ni.getName=> "eth0"> ni.isUp=> true> ni.getMtu=> 1500> ni.inetAddresses.map { |addr| addr.to_s }=> ["/fe80:0:0:0:20c:29ff:fead:4bed%2", "/192.168.96.129"]
Flying Saucerを使ってHTMLをPDF変換http://code.google.com/p/flying-saucer/
Java連携 (Ruby -> Java)
% ls flyingsaucer-R8core-renderer.jar iText-2.0.8.jar ...% jruby -S irb -Iflyingsaucer-R8> require 'java'> require 'iText-2.0.8.jar'> require 'core-renderer.jar'> rr = org.xhtmlrenderer.pdf.ITextRenderer.new> doc = <<EOD<html><body><h1>Hello JRuby</h1><p>from <a href="http://code.google.com/p/flying-saucer/">Flying Saucer</a>.</p></body></html>EOD> rr.set_document_from_string(doc)> rr.layout> File.open("out.pdf", "w") { |f| rr.create_pdf(f.to_outputstream) }
RubyとJRubyの利点を活かしてJavaをテスト
RSpec:振る舞いをテストhttp://rspec.info
Javaテスト (RSpec)
describe 'ScriptingContainer#put' do before :each do @x = org.jruby.embed.ScriptingContainer.new end it "sets an object to local variable" do obj = Object.new @x.put("var", obj) @x.run_scriptlet("var").should == obj end it "overrides the previous object" do obj = Object.new @x.put("var", obj) @x.put("var", nil) @x.run_scriptlet("var").should be_nil endend
% jruby -S rspec jruby_spec.rb..
Finished in 0.044 seconds2 examples, 0 failures%
Javaテスト (JtestR)
JtestR: 各種Ruby用テストライブラリ同梱http://jtestr.codehaus.org/
describe "X509Name" do it "should use given converter for ASN1 encode" do converter = mock(X509NameEntryConverter) name = X509Name.new('CN=localhost', converter) converter.stubs('getConvertedValue'). with(DERObjectIdentifier.new(CN), 'localhost'). returns(DERPrintableString.new('converted')). times(1) name.toASN1Object.to_string.should == '...' endend
Javaテスト (JtestR)
Ant/Maven統合 + テストサーバ
% ant testBuildfile: /path/to/build.xml
test: [jtestr] Other Spec: 4 examples, 0 failures, 0 errors [jtestr] [jtestr] Total: 4 tests, 0 failures, 0 errors, 0 pending [jtestr]
BUILD SUCCESSFULTotal time: 9 seconds
<?xml version="1.0" encoding="utf-8"?><project basedir="." default="test" name="simple1"> <taskdef name="jtestr" classname="org.jtestr.ant.JtestRAntRunner" classpath="build_lib/jtestr.jar" /> <taskdef name="jtestr-server" classname="org.jtestr.ant.JtestRAntServer" classpath="build_lib/jtestr.jar" /> <target name="test"> <jtestr port="20333"/> </target> <target name="test-server"> <jtestr-server port="20333" runtimes="3"/> </target></project>
開発支援 (Ant連携)
Rake: Rubyの記述力を活かしてビルド手順を記述 Ant、Rakeから相互にタスクを利用可能
desc "Build JRuby"task :build do ant "jar"endtask :jar => :builddesc "Clean all built output"task :clean do delete_files = FileList.new do |fl| fl. include("#{BUILD_DIR}/**"). exclude("#{BUILD_DIR}/rubyspec"). include(DIST_DIR). include(API_DOCS_DIR) end ...<target name=”load-rake-task”>
<taskdef name=”rake” classname=”org.jruby.ant.Rake”/></target><target name=”default” depends=”load-rake-task”> <rake task=”jar”/></target>
開発支援 (Maven連携)
Maven配布物はrubygemsとしてインストール可能
開発環境の部分的Ruby化を支援
% jruby -S gem install bouncycastle:bcprov-jdk15
require 'rubygems'require 'maven/bouncycastle/bcprov-jdk15'...
Ruby Plugins for Jenkinshttp://bit.ly/JenkinsRuby JenkinsのプラグインをRubyで記述可能
開発支援 (Jenkins連携)
開発支援 (Jenkins連携)例: Travis CI設定を読んで自動ビルド
class TravisScriptBuilder < Jenkins::Tasks::Builder def prebuild(build, listener) travis_file = build.workspace + '.travis.yml' unless travis_file.exist? listener.error "Travis config `#{travis_file}' not found" raise "Travis config file not found" end ... def perform(build, launcher, listener) run_scripts(setup_env) ... def run_scripts(env) %w{before_script script after_script}.each do |type| scan_multiline_scripts(config[type]).each do |script| launcher.execute(env, script, :chdir => workspace, :out => listener) ...
ウェブ開発 (JRuby on Rails)
Ruby on Rails - http://rubyonrails.org
ウェブアプリケーションフレームワーク フルスタック CoC: (XML)設定より規約(に従って開発)
DRY: 同じことを繰り返さない
ウェブ開発 (JRuby on Rails)
Railsの全ての機能 + 既存Javaライブラリ活用
Javaアプリと同居可能
SpringMVCからRailsへのリファクタリング事例
1) "Petclinic"にJRubyでREST APIを追加
2) Railsの同居
3) Spring利用の機能をRailsで置き換えhttp://bit.ly/refactoring-to-rails
JRuby on Railsのデプロイ
WAR形式 → 任意のJavaアプリサーバで動作
専用アプリサーバ:
Trinidad(Tomcatベース)
https://github.com/trinidad/trinidad
TorqueBox(JBossベース)
clustering、messaging、scheduling他
http://torquebox.org/
まとめ: JRuby - http://jruby.org/
JavaとRuby両方の豊富な資産を利用可能
38624 in search.maven.org36713 in rubygems.org (as of 20120403)
現実世界で使われるフレームワーク、ライブラリ
Rails、RSpec、JtestR、Jenkins、scripting、DSL Java開発者のツールベルトに