RailsのController実装をJavaで作る
前回でViewにJSPを使えるようになったので今回はController。
とりあえず以下のようなJavaのActionクラスをRailsのControllerにマッピングすることを考える。
package sample; public class SampleAction { public Integer result; public String v1; public String v2; public String index(){ return "test.jsp"; } public String add(){ result = Integer.parseInt(v1) + Integer.parseInt(v2); return "test.jsp"; } }
publicメソッドがRailsのControllerで定義したのと同じような感じで使えるようになればいい。
publicフィールドは入出力に使うこととする。
Rails Controller
JavaActionというJRubyの自作Moduleをjava_action_module.rbファイルとして作成しておいて、RailsのControllerではそれを利用してJavaのクラスで宣言されたメソッドを自動的にマッピングすることを考える。
(このコード自体をJavaで作ることも可能かもしれないが、めんどいのでやめておく)
require 'java_action_module' class PollsController < ApplicationController include JavaAction def initialize add_methods("sample.SampleAction") end end
module JavaAction
さてここからが本題。
Javaクラスのメソッド情報を取得して、Rubyでメソッドを動的に定義できればよい。
また、JavaクラスのフィールドをViewとの入出力で使えるようにする必要がある。
まずは、後でpublicメソッド・フィールドを取得するときのためにMemberにpublic?メソッドを定義しておく。(Javaの既存クラスにメソッドを追加定義するにはJavaUtilities.extend_proxyを使う)
module JavaAction JavaUtilities.extend_proxy "java.lang.reflect.Member" do def public? java.lang.reflect.Modifier.isPublic(self.getModifiers) end end
Method,FieldはMemberインターフェースを実装しているので、Method,Fieldでもpublic?メソッドが使えるようになった。
Javaのインターフェースにもメソッドを追加定義できるのがJRubyの面白いところ。
次は、ViewにJSPを使うためのコード(JSP実行結果の取得)。前回のとほぼ同じ
def jsp(path) writer = java.io.StringWriter.new req = request.env['java.servlet_request'] resp = javax.servlet.http.HttpServletResponse.impl do |method| case method when :getWriter java.io.PrintWriter.new(writer) else logger.warn "method:#{method} called" end end $servlet_context.getRequestDispatcher(path).include(req, resp) writer.toString end
そして、メソッドを動的追加するためのメソッド。
まずは、Javaクラス内で宣言されたpublicメソッド・フィールドの配列を取得する
(.select &:public? の部分でpublicなものだけ取るようにしている)
def add_methods(java_class) clazz = java.lang.Class.forName(java_class) methods = clazz.declared_methods.select &:public? fields = clazz.declared_fields.select &:public?
次に呼び出し元のファイルパスからViewファイルが入ったディレクトリのパスを取ってくる。
(RequestDispatcherに渡すときのパスに使うので/WEB-INF/から始まる文字列になる)
path = caller[0].sub(%r!.*(/WEB-INF/.*/app/).*/(.*)_controller.rb.*!, '\1views/\2/')
Javaクラスのメソッドごとにdefine_methodで動的にメソッドを作成する。
define_methodはModuleのコンテキストで呼ぶ必要があるのでmodule_eval内で呼ぶこととなる。
methods.each do |m| JavaAction.module_eval do define_method(m.name) do
define_methodの中では、まずHttpServletRequestの取得とJavaクラスのインスタンス化をして、
インスタンスのフィールドにリクエストのパラメータを入れて、
インスタンスのメソッド実行をする
req = request.env['java.servlet_request'] instance = clazz.newInstance fields.each do |f| instance.send("#{f.name}=", params[f.name]) unless params[f.name].nil? end page = instance.send(m.name)
最後にリクエストのAttributeにフィールドの名前と値を入れて、JSP呼び出しを行って完了。
fields.each{|f| req.setAttribute(f.name, f.get(instance))} render :inline => jsp(path + page), :layout => true end end end end end
これでとりあえずJavaでのController実装ができることがわかった。
ただし、まだいろいろ課題がある。
バリデーションとかリダイレクトの対応も必要だし、JSPでカスタムタグ使いたいよね
何かフレームワークとか使えるのあるだろうか・・・