KLab若手エンジニアの これなぁに?

KLab株式会社の若手エンジニアによる技術ブログです。phpやRubyなどの汎用LLやJavaScript/actionScript等のクライアントサイドの内容、MySQL等のデータベース、その他フレームワークまで幅広く面白い情報を発信します。

Java

ソーシャルアプリの作り方 第二回 - GAE ホスティング

umjammer です、 ソーシャルアプリの作り方の第二回になります。 第一回目で Mixi アプリを作成しましたが、それをホスティングする必要があります。また外部 API としてハイスコア管理をしたいので、それを行う Web App を作成してホスティングする場所が必要になります。 今回はある程度の規模までならタダで、しかも簡単にスケールしてくれる Google App Engine で Mixi アプリをホスティングすることにします。
GAE Logo


まず Mixi アプリですが ガジェット XML やその他 html, javascript は普通に配置します。
OpenSocial, GAE


次に外部 API のハイスコア管理を実装するために Web App を実装します。

足回り

GAE はどこからでもアクセスできてしまいます。そのため Mixi アプリ以外からのリクエストを遮断するために OAuth を使用します。 OAuth は OpenSocial でも使用されている API 認可方式です。Mixi API は OpenSocial ベースなので同じものを採用します。 サーブレットフィルタとして実装されているソフトウェアが存在するので利用させていただきます。 フィルタ実装ですと Web App は認可を気にせずコーディングできるので大変便利です。Mixi から取得した公開鍵、アプリID, ガジェットXMLのURL を設定して、web.xml にフィルタとして登録します。


Mixi 公開鍵の URL

Web App 実装

外部 API は簡単に作成するために REST API のフレームワークを使用します。JSR-311 のリファレンス実装である Jersey を採用してみました。アノテーションと DI が使えるのでかなり楽にプログラムが組めます。HTML のテンプレート等を簡単に扱えるレンダラがあれば言うこと無しなのですが。 http://code.google.com/p/umjammer/source/browse/trunk/vavi-apps-gae03/src/main/java/vavi/apps/umjammer03/lr/LodeRunnerResource.java Data Store に関しても楽をしたかったので SpringFramework の AOP Transaction を使用した JPA で書いてみました。私トランザクションの例外処理を書くのが大嫌いなんです。 http://code.google.com/p/umjammer/source/browse/trunk/vavi-apps-gae03/src/main/java/vavi/apps/umjammer03/lr/MixiFeint.java http://code.google.com/p/umjammer/source/browse/trunk/vavi-apps-gae03/src/main/java/vavi/apps/umjammer03/lr/MixiFeintService.java あとはチューニングとして Cache API なども使用します。

まとめ

フレームワークを使いすぎました。Web App 自体は簡単に作成することができましたが、実際に動かしてみると spin-up にものすごく時間がかかります。 GAE/J はこのあたりを気にしないといけないのが辛いところですね。 ともあれ、SpringFramework や JSR-311 などは何も気にせずに GAE/J に載せることができたので、アプリケーションを簡単に GAE でホスティングすることができました。 次回はこのシリーズの目玉になります。お楽しみに。

ソーシャルアプリの作り方 第一回 - Mixi アプリ

umjammer です。 ソーシャルアプリ、盛り上がっていますね。 クライアントが多いゲームだとユーザー数が100万人単位とか、B2Cシステムの極みともいえる大規模分散処理が必要とされるインフラが求められます。そんな大掛かりなアプリケーションを作成する場合は、手前味噌になりますが DSAS Hosting for Social 等を利用することになるでしょう。 一方、小ぢんまり始めるのなら世の中にはソーシャルアプリを動かすインフラがすでに用意されています。 このシリーズでは簡単に始めてしまおうをコンセプトに、ソーシャルアプリの作り方を三回にわたって紹介していこうと思います。
Social Application Map


まずは第一回として Mixi アプリを作ってみます。 Mixi アプリに限らず、ソーシャルアプリは基本 JavaScript もしくは Flash で作成しますが、一から作るとなると面倒ですよね。しかし良く考えてみるとブラウザと連携が可能なプラットフォーム、つまりプラットフォームから JavaScript にアクセス出来る仕組みさえあれば、開発は JavaScript に限らず行うことができます。最近では 3D プラットフォーム なども存在します。 今回はちょうど手元にあった、いにしえの Java Applet のプログラムを移植してみます。ブラウザで動く Java には LiveConnect というブラウザとの連携 API が存在します。すべてのブラウザに載っているわけではないのですが Firefox 3.6, Safari 4 等は対応していますのでまあ良しとしましょう。 Mixi アプリはダウンロード元が Mixi のホストになります。Java Applet は自前でホスティングしています。Java Applet はクロスドメイン制約があるため直接 Mixi ホストの OpenSocial API にアクセスすることはできません。よって LiveConnect を使用して JavaScript の関数をを呼ぶことになります。 Java Applet から JavaScript 関数を呼ぶ (ハイスコア)

    JSObject window = JSObject.getWindow(applet);
    window.call("setScore", 1000);

JavaScript 側 (外部 API)

    function setScore(score) {
      var params = {};
      params[gadgets.io.RequestParameters.AUTHORIZATION] =
          gadgets.io.AuthorizationType.SIGNED;
      gadgets.io.makeRequest(url + "/" + score, setScoreCallback, params);
    }
JavaScript 側で OpenSocial API や外部 API 呼出しを行ってあげれば Applet で OpenSocial API を制御できます。 また JavaScript 側の API 呼び出しは非同期が基本ですので、 Java 側にあとで値を設定する場面が出てきます。 JavaScript から Java メソッドを呼ぶ

document.applets["appletName"].java_method(args)

出来上がりはこんな感じ、
Mixi Lode Runner


ゲームオーバーになると自分のスコアが外部 API 呼び出しで登録され、ハイスコアランキングが更新されるようになっています。 版権モノなので残念ながら Mixi では一般公開はしていません。 Java Applet で Mixi アプリのソース まとめ Java Applet で Mixi アプリができました。あなたの PC の中でホコリを被っている Applet や、ここで紹介している方法 で iAppli なども Mixi アプリにすることが可能になりますよ!もちろん Flash や JavaScript のゲームですともっと簡単に移植できますね!みなさんが持っていて使わなくなったゲームを再び日の当たる場所に出してあげませんか?

Java Native Access (JNA) で遊んでみる

umjammer です、 社内の Tech ML に同僚が Java Native Access 面白そうだよと投稿したのを見て、私も Java Native Interface (JNI) はよく触る方なので興味をそそられました。試してみようと今までに JNI で書いた物を JNA に適用することにしました。 題材としては、日本語をしゃべってくれる AquesTalk を JNI でラップしたライブラリを以前作ったことがあったので、それを使用します。 AquesTalk は C のライブラリでヘッダーファイルは以下のように定義されています。 C Header http://vavi-sound.googlecode.com/svn/trunk/vavi-speech/etc/include/AquesTalkDa.h それを JNA の方法にのっとって Java のクラスに変換します。 JNA http://vavi-sound.googlecode.com/svn/trunk/vavi-speech/src/main/java/vavi/speech/aquestalk/jna/AquesTalkDa.java 以前書いた JNI だとこんな感じでした。 JNI http://code.google.com/p/vavi-sound/source/browse/trunk/vavi-speech/src/main/jni/win32/AquesTalkWrapper.c AquesTalk のライブラリが簡単なメソッドの構造なので JNA に適用するのは簡単でした。 JNI と違い C のコードを一切書かなくて済むので楽ですね。 できたところで日本語をしゃべらせようとしましたが、AquesTalk は単語辞書を持っていませんので、漢字を読むことができません。ついでなので形態素解析を使用して漢字も読めるようにしてみました。さらについでに Java Speech API 1.0 にも対応させてみました。形態素解析は sen を使用しました。 JSPAI + 形態素解析 http://code.google.com/p/vavi-sound/source/browse/trunk/vavi-speech/src/main/java/vavi/speech/aquestalk/jsapi/AquesTalkSynthesizer.java 今までのコードはこちら JNA 化している最中に気づいたのですが AquesTalk ってあれの中の人だったんですね。

    synthesizer.speakPlainText("ゆっくりしていってね", null);
    synthesizer.speakPlainText("漢字も読めるよ", null);

AquesTalk は Windows 用を使用したのですが、実は開発は Mac で行っています。wine で AquesTalk 動かせちゃいます。 最後に、 調べていくうちに JNAerator なるものを見つけました。C のヘッダーファイルを食わせて JNA に変換してくれるものみたいです。複雑な C のコードもこれだと楽ができるかもしれません。 AquesTalk2 なるものもリリースされています。Mac や iPhone のライブラリまであります。音声も自分で作れるみたいです。いろいろ楽しいことになりそうです。

制約があるユニットテスト方法その2

umjammer です、 携帯のサイト向けコードでは、時刻を扱うのにデバッグしやすいように仮想カレンダーが仕込まれておりメソッドの引数にも時刻が設定できたりします。この場合は問題ありません。 一方管理ツールでは現在の時刻を元に処理を行う場面が多く、コードの引数には時刻が存在せず今の時刻を前提にした処理が多く存在します。 今の時刻を前提にされると、テストに用いるデータも現在の時刻を前提にしないといけません。テストする時刻に合わせて毎回データを作るなんてやってられません。 # テスト駆動開発支持者からは時刻の引数をテストのために作っとけよと言われそうですが、 # 実際著名なライブラリでテストのために余計な引数を設けてるライブラリなんか # 見たことありませんよね?この辺は別の議論になるので割愛します。 で、どうするか? 今回はスクラッチされたコードではなく、時刻というオリジナルクラスの値を変更するので Java Instrumentation という、クラスのローディング時にバイトコードを書き換えられるという機能を用いて、オリジナルクラスである java.util.Calendar クラスが返す値を常に一定にするように書き換えてやりました。 バイトコードの書き換えは javassist を今回は使ってます。 こんな感じです、

public class TestInstrumentation {

    /** */
    private static ClassPool classPool;

    /**
     * @param agentArgs year,month,day
     */
    public static void premain(String agentArgs,
                               Instrumentation instrumentation) {
        classPool = ClassPool.getDefault();
        String[] args = agentArgs.split(",");
        final int year = Integer.parseInt(args[0]);
        final int month = Integer.parseInt(args[1]) - 1;
        final int day = Integer.parseInt(args[2]);

        instrumentation.addTransformer(new ClassFileTransformer() {
            /** */
            public byte[] transform(ClassLoader loader,
                                    String className,
                                    Class classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer)
                throws IllegalClassFormatException {

                if (className.equals("java/util/Calendar")) {
                    try {
                        ByteArrayInputStream stream =
                            new ByteArrayInputStream(classfileBuffer);
                        CtClass ctClass = classPool.makeClass(stream);

                        CtMethod ctMethod = ctClass.getDeclaredMethod(
                            "getInstance",
                            new CtClass[] { classPool.get("java.util.Locale") });
                        ctMethod.insertAfter(
                            "$_.set(" + year + ", " + month + ", " + day +
                            ", 12, 0, 0);" +
                            "System.err.println(\"javassist: fix calendar: " +
                            year + "-" + (month + 1) + "-" + day + " " +
                            "12:00:00" + "\");" +
                            "return $_;");

                        return ctClass.toBytecode();
                    } catch (Exception e) {
                        throw (IllegalClassFormatException)
                            new IllegalClassFormatException().initCause(e);
                    }
                } else {
                    return null;
                }
            }
        });
    }
}
テストするときは、これを使って時刻を固定するとテストデータもその時刻の物だけで済むようになります。 JVM 起動時には上記プログラムを jar にして VM オプションとして

  -javaagent:foo.jar=2008,5,14

などと、指定してやります。 その1の Aspect 使うのとどう違うの?と言われると、こちらの方が万能なので Aspect の方はこの方法で代替できます。ただこちらは VM の引数を増やしたり、jar を作っておかないといけないとか面倒なのと、いろいろな方法を紹介したかったので分けています。 また、この方法もバイトコードが書き換え可能な言語なら適用できますので、お試しください。

制約があるユニットテスト方法その1

umjammer です、 ユニットテストを行うに際して、本番で使うデータが存在するディレクトリとは違うテスト用のデータが入っているディレクトリを使用したい、といった場面でどうするか?という方法です。 本来なら、そんなのちゃんと設計して設定ファイルで変更できるようにしておけよ、と言われる場面ですが多人数が関わる大規模プロジェクトになるとそうは言ってられない事態に出くわすことが多々あります。他のメンバーが作ったクラスのユニットテストを後から作る場面も出てくる訳です。 で、どうするか? 今回はアスペクト指向プログラミングの実装である AspectJ を用いて実行時にメソッドが返す値を 書き換えてやりました。 実際のコードはこんな感じになります。 SpringAOP を使用した例 spring-beans.xml

  <aop:config>
    <aop:aspect id="testFooAspect"
                ref="testFooAroundAdvice">
      <aop:around pointcut-ref="aroundBar"
                  method="aroundBar" />
      <aop:pointcut id="aroundBar"
                    expression="execution(* org.klab.foo.Foo.bar(..))" />
    </aop:aspect>
  </aop:config>

  <!-- AroundAdvice クラスに書き換えたい値を DI -->
  <bean id="testFooAroundAdvice"
        class="org.klab.foo.AroundAdvice">
    <property name="wantToReplace"
              value="replace_value" />
  </bean>
AOP 本体のクラス

package org.klab.foo;

public class AroundAdvice {

    private String wantToReplace; // DI される書き換えたい値

    public void setWantToReplace(String wantToReplace) { // DI 用
        this.wantToReplace = wantToReplace;
    }

    public Object aroundBar(ProceedingJoinPoint pjp) throws Throwable {

        Foo foo = Foo.class.cast(pjp.getArgs()[0]);

        // wantToReplace を foo 内で書き換える        

        Object result = pjp.proceed(new Object[] { foo });

        return result;
    }
}
Java に限らず実行時にコードを書き換えられる言語だと可能な技なので、試してみはいかがでしょうか?
 KLab若手エンジニアブログのフッター