裏紙 https://backpaper0.github.io/ I ❤️ BotW. en-us Thu, 22 Feb 2018 00:00:00 +0000 https://backpaper0.github.io/2018/02/22/spring_proxy.html https://backpaper0.github.io/2018/02/22/spring_proxy.html <![CDATA[Springのproxyとfinalメソッド、それからnull]]> Springのproxyとfinalメソッド、それからnull

こざけさんがこんな感じでわちゃわちゃしていたので、詳しい資料はどこにあるか分からないけれど、なぜそうなったのか解説しようと思います。

なぜそうなるのか概ね理解していたけれど、理解していなかった点を調べる過程で私自身も学ぶことがあったので、正直こざけさんには調べるきっかけを作ってくれてありがとうございますという感じです!

@Repositoryとproxy

@Repository をクラスに付けると、そのクラスのproxyが作られます。

proxyてなんやねんって話ですが、これは実行時に生成される該当クラスのサブクラスで、他のコンポーネントにインジェクションされるのはこのproxyのインスタンスです。 実際のクラスのインスタンスへはproxyのインスタンスを経由して委譲される仕組みになっています。

このproxyの仕組みには、コンポーネントの利用者がスコープを意識しなくてよくなるという利点があります。

どういうことか、順を追って見ていきましょう。

proxyという概念が無いDIコンテナ

DIコンテナにはスコープというものがあります。

これはコンポーネントのライフサイクルを表すもので、Webアプリで動くDIコンテナであれば「リクエストスコープ」や「セッションスコープ」を持っています。 「リクエストスコープ」はその名の通りHTTPリクエストの開始から終了までの間に有効となるスコープです。 「セッションスコープ」もその名の通りでセッションを開始してから破棄されるまでの間に有効となるスコープです。

では「セッションスコープのコンポーネント」から「リクエストスコープのコンポーネント」を使用することを考えてみましょう。 コードで書くとこんな感じ。

//※これはSpringではない架空のDIコンテナのコード

@SessionScope
public class Hoge {

    @Inject
    private Fuga fuga;

    public void action() {
        fuga.process();
    }
}

@RequestScope
public class Fuga {
    //フィールドやメソッドの定義は省略
}

この HogeFuga を使った処理の流れは、

  1. HTTPリクエストを受け取る
  2. セッションを作成する
  3. Hoge のインスタンスを作成する
  4. Fuga のインスタンスを作成する
  5. HogeFuga をインジェクションする
  6. Hogeaction メソッドを実行する
  7. Fugaprocess メソッドを実行する
  8. HTTPリクエストが終了する
  9. Fuga のインスタンスをDIコンテナから破棄する
  10. HTTPレスポンスを返す

という感じ。

あんまり問題なさそうに見えますが、 Fugaprocess メソッドが実行中に同じユーザーからもう一つHTTPリクエストが来たらどうなるでしょうか?

  1. HTTPリクエストAを受け取る
  2. セッションを作成する
  3. Hoge のインスタンスを作成する
  4. Fuga のインスタンスを作成する(リクエストAのスコープ)
  5. HogeFuga をインジェクションする
  6. Hogeaction メソッドを実行する
  7. Fugaprocess メソッドを実行し始める
  8. HTTPリクエストBを受け取る
  9. Fuga のインスタンスを作成する(リクエストBのスコープ)
  10. HogeFuga をインジェクションする……!?

とまあ、こんな感じで Hoge にインジェクションされる Fuga がバッティングするわけです。

そういうわけでproxyの無いDIコンテナでは、あるスコープのコンポーネントには、それよりも小さいスコープ(ライフサイクルが短いと言い換えても良さそう)のコンポーネントをインジェクションできませんでした。 proxyが無くてこのような制約のあるDIコンテナとしてはSeasar2が挙げられます。

proxyがあるDIコンテナ

先に述べた問題をproxyがどのように解決するのか見ていきましょう。

Fuga のproxyをコードで書くとこんな感じになります。

//実際には実行時にクラスファイルが生成されるのでソースコードは存在しない
//あくまでもproxyのイメージ
public class FugaProxy extends Fuga {

    private Container container;

    public void process() {
        Fuga component = container.getComponent(Fuga.class);
        component.process();
    }
}

コード上のコメントにも書きましたが、proxyは実際には実行時にクラスファイル(というかインメモリ上にバイトコードのデータ)として生成されるのが普通なので、ソースコードはありません。 コード例は雰囲気です。

コードを見てみると process メソッドが呼ばれるとDIコンテナからproxyではない実際の Foo インスタンスが取り出され、そのインスタンスの process メソッドが呼ばれています。

先ほどのproxyが無いDIコンテナで Fuga がバッティングしてしまったシナリオを、proxyがあるDIコンテナではどうなるか見てみましょう。

  1. HTTPリクエストAを受け取る
  2. セッションを作成する
  3. Hoge のインスタンスを作成する
  4. Fuga のインスタンスを作成する(リクエストAのスコープ)
  5. Fuga のproxyを作成する
  6. HogeFuga のproxyをインジェクションする
  7. Hogeaction メソッドを実行する
  8. Fuga のproxyの process メソッドを実行し始める
  9. Fuga のproxy内でDIコンテナから実際の Fuga インスタンス(リクエストAのスコープ)を取り出して process メソッドを実行し始める
  10. HTTPリクエストBを受け取る
  11. Fuga のインスタンスを作成する(リクエストBのスコープ)
  12. Hogeaction メソッドを実行する
  13. Fuga のproxyの process メソッドを実行し始める
  14. Fuga のproxy内でDIコンテナから実際の Fuga インスタンス(リクエストBのスコープ)を取り出して process メソッドを実行し始める

このようにproxyを経由してDIコンテナから異なる Fuga インスタンスを取り出して process メソッドを実行できました。

以上のことからproxyがあるDIコンテナではスコープを意識することなくインジェクションを行えることが分かりました。

finalメソッドからフィールドを参照したらnullになった理由

当初の問題に戻りましょう。 「 @Respository を付けたクラスの final なメソッドを実行するとインジェクションされたはずのフィールドへアクセスしたときに NullPointerException が発生した」という問題です。

次のコードを見てください。

@Repository
public class Foo {

    @Autowired
    private Bar bar;

    public String method1() {
        return String.format("%s%n%s%n", bar, getClass());
    }

    public final String method2() {
        return String.format("%s%n%s%n", bar, getClass());
    }
}

method1method2 はどちらもフィールド bar と自分自身のクラスを文字列にして返しています。 異なる点は method2final であるということだけです。

method1 の実行結果はこちら。

com.example.demo.Bar@1db75e25
class com.example.demo.Foo

method2 の実行結果はこちら。

null
class com.example.demo.Foo$$EnhancerBySpringCGLIB$$a65476ff

当初の問題と同じように final な方の method2 では barnull になっていますね。

ただ、クラス名にも注目してください。 method1 では Foo となっていますが method2Foo のproxyになっています。

ここで Foo のproxyがどうなっているのか、再び雰囲気でコードにしてみましょう。

//雰囲気
public class Foo$$EnhancerBySpringCGLIB$$a65476ff extends Foo {

    Container container;

    @Override
    public String method1() {
        Foo component = container.getComponent(Foo.class);
        return component.method1();
    }

    //method2はfinalなのでoverrideできない
}

ご覧の通り method1 はコンテナから実際のインスタンスを取り出して委譲していますが、 method2final なため override できずに実際のインスタンスに委譲されることなく実行されてしまいます。

proxyは、コンテナから実際のインスタンスを取り出して委譲するものなので、proxyに対してコンポーネントをインジェクションする意味はありません。 実際、元のクラスでインジェクション対象となっているフィールドはproxyでは null になります。

以上が、proxyが作られるクラスに定義された final メソッドがインジェクション対象のフィールドを参照している場合に NullPointerException になる理由です。

コンストラクタインジェクションとproxy

さて、ここからが私も初めて知った事柄になります。 久しぶりにびっくりした。

次のようにコンストラクタインジェクションしているクラスを考えてみましょう。

@Repository
public class Foo {

    private final Bar bar;

    public Foo(Bar bar) {
        this.bar = Objects.requireNonNull(bar);
    }

    public String method1() {
        return String.format("%s%n%s%n", bar, getClass());
    }

    public final String method2() {
        return String.format("%s%n%s%n", bar, getClass());
    }
}

proxyの雰囲気コードはこんな感じ。

//雰囲気
public class Foo$$EnhancerBySpringCGLIB$$a65476ff extends Foo {

    Container container;

    public Foo$$EnhancerBySpringCGLIB$$a65476ff(Bar bar) {
        super(bar);
    }

    @Override
    public String method1() {
        Foo component = container.getComponent(Foo.class);
        return component.method1();
    }

    //method2はfinalなのでoverrideできない
}

この場合、生成されるproxyのコンストラクタ引数 bar には何が入るのでしょうか?

先に述べた通り、proxyのインジェクション対象フィールドにはインジェクションする意味はありません。 かと言って null を渡すとスーパークラスのコンストラクタで Objects.requireNonNull によって NullPointerException がスローされます。

それではSpringはproxyをインスタンス化するときに何を渡すのでしょうか?

色々とがんばってソースコードを追っかけた末に分かったのですが、コンストラクタを呼ばずにインスタンス化していました。 何を言っているのか分かりませんね?

proxyのインスタンス化にはObjenesisというライブラリの次のクラスが使われていました。 (実際には org.springframework.objenesis にrepackageされています)

クラスのJavadocに次の記載があります。

Instantiates an object, WITHOUT calling it’s constructor, using internal sun.reflect.ReflectionFactory

お、おう……! って感じですが、JDKのinternalなクラスを使ってコンストラクタを呼ばずにインスタンス化していました。 だからフィールドが null だった、というわけですね。

コンストラクタ呼ばれないってどういうことだよ……と思いながら SunReflectionFactoryInstantiator を眺めていたら newConstructorForSerialization というメソッド名が出てきて、そういえばシリアライズされたオブジェクトをデシリアライズする時ってコンストラクタ呼ばれないんだっけ、とか雑な記憶で雑に思いました。 とか書いておくと詳しい人がコメントくれるはずです。 他力本願。

まとめ

  • DIコンテナにはproxyを経由してスコープを意識せずに使える機能がある
  • proxyは実行時にサブクラスを生成して実現するので final メソッドを使うとマズい
  • SpringではproxyはJDKのinternalなクラスを使用してコンストラクタ呼び出し無しにインスタンス化される(その結果フィールドが null になる)

こんな感じで、割と真っ黒な黒魔術に辿り着いた感があって楽しかったです。 こざけさん、ありがとう!

ちなみに

INFO レベルですが、ログは出ているみたいです。

018-02-22 22:22:14.298  INFO 25770 --- [  restartedMain] o.s.aop.framework.CglibAopProxy          : Final method [public final java.lang.String com.example.demo.Foo.method2()] cannot get proxied via CGLIB: Calls to this method will NOT be routed to the target instance and might lead to NPEs against uninitialized fields in the proxy instance.
]]>
Thu, 22 Feb 2018 00:00:00 +0000
https://backpaper0.github.io/2017/04/17/acrobook.html https://backpaper0.github.io/2017/04/17/acrobook.html <![CDATA[「Java本格入門」を読んだ]]> 「Java本格入門」を読んだ

せろさん に献本して貰いました。 ありがとうございます!

この本はJavaの入門書ではありますが「本格」と付いているだけあって、

  • コレクションの実装に踏み込んでどの場面でどの実装を使うべきか
  • Map#computeIfAbsent の現場あるあるな使い方
  • 正規表現関連のオブジェクト( PatternMatcher )と String のメソッドの使い分け

など、実践的な内容が含まれています。

また、Java 8を前提として書かれてはいますが、 ファイル操作に関してはJava 6までのやり方( java.io )とJava 7以降のやり方( java.nio.file )が、 日時操作に関してはJava 7までのやり方( java.util.Datejava.util.Calendar )とJava 8以降のやり方( java.time )が両方書かれています。 趣味プログラミングならいつでも最新のバージョンを使っていれば良いですが、仕事では案件によっては保守だけに徹しており古いやり方でコードを読み書きする必要がある場合もあるでしょう。 この本ならレガシーと最新のどちらのやり方も学ぶ事ができます。

他にも、文法的に許可されてはいるが実践的な観点から書かない方が良いコードを教えてくれたり、 インターフェースと抽象クラスの良い使い分け方を示してくれたり、 執筆陣の知識・経験を元に書かれていることが単なる初心者向けの入門書とは異なるところだと感じました。

私の感覚でいうと、まったくの新人ではなく現場に出て2年目あるいは3年目ぐらいがこの本を読むのに適したレベルかな、と思います。

間違いと思われる記述

いくつか間違いがあったので挙げておこうと思います。

35ページ、 >> 演算子の説明

正負の符号を表すビットは保持し、それ以外の空いたビットは0埋めする。

右方向の算術シフトは空いたビットを0埋めするのではなく、符号と同じビットで埋めます。

82ページ、インターフェースの説明

インターフェースは必ずpublicになるため、インターフェース名の前に書くpublicは省略することができます。

インターフェース名の前の public を省略するとパッケージプライベートになります。

また、ネストしたインターフェースは private にすることもできます。

156ページ、メソッド参照の文法

これは間違いというより、表の内容が足りていないと思われます。

staticメソッドを参照する記法のひとつとして次のものを挙げています。

{クラス名}::{メソッド名}

でもこれ、場合によってはインスタンスのメソッドを参照する記法としても使えます。

例えば、次のようなコードです。

//{クラス名}::{メソッド名}の記法でインスタンスのメソッドを参照している
ToIntFunction<String> f = String::length;
int i = f.applyAsInt("foobar");

197ページ、ラムダ式内での例外について

ラムダの中に記述した処理で例外が発生する可能性があります。 それが検査例外だった場合は、捕捉しないと、コンパイルエラーが発生します。

Stream APIで使われるラムダ式、つまり FunctionPredicate であればそうですが、 例えば Callable のラムダ式であれば call メソッドは throws Exception と宣言されているので Exceptioncatch する必要はありません。

その他の軽い間違い

108ページ、フィボナッチ数を求める定義とコードが書かれていますが、定義を見ると F0 = 0 F1 = 1 Fn = ... となっていますが、 直後に記載されている昔ながらの for ループを使ったコードは i <= 1 のときに 1 を代入しているので、 定義とコードが合ってないですね。

それと134ページ、「Mapインターフェースでの要素の取得、変換」に値の集合を取得するメソッドとして valueSet が挙げられていますが values の間違いだと思います。 なお、 values の戻り値は集合( Set )ではなくコレクション( Collection )です。

あと、めっちゃ細かいところで言うと、183ページにAutoClosable、Closableという記述がありますが、 正しくはAutoCloseable、Closeableです。 (めっちゃ分かりづらいですが……)

まとめ

このようにいくつか間違いと思われる記述もありましたが、 これらは前半の章(文法の解説などのまさに入門的な章)に集中していました。

読み進めるにつれてより実践的な内容が増えており、 現場で使える知識を付けることができるのではないでしょうか。

]]>
Mon, 17 Apr 2017 00:00:00 +0000
https://backpaper0.github.io/2016/12/31/good_bye_2016.html https://backpaper0.github.io/2016/12/31/good_bye_2016.html <![CDATA[2016年ふりかえり]]> 2016年ふりかえり

今年の活動

今年は意識的にいろいろ登壇するぞー、と昨年から決めていました。

結果は次の通り。

登壇以外で言うと、いろふさんと二人でWEB+DB PRESSで「Javaの新定石」の連載をしたのが大きいです。

Java Day TokyoとSpring Dayの登壇、それからWEB+DB PRESSの連載は会社名を出して行いましたが、 出張扱いで交通費も完全に出して貰ったし、これらの社外活動も評価して貰えているので、弊社は良い中小SIerだなー、と思いました。

それから、これはネタ以外のなにものでもないけれど、 セミコロンレスJavaで戻り値のあるメソッドを実装してのけました 。 この時ばかりは、自分を天才かと思いました。

来年に向けて

私のGitHubリポジトリのアクティビティログには顕著に現れていますが、 最近は完全にSpringへ傾倒しています。

今年は表面の美味しいところをぺろぺろした感じなので、 来年は奥まで覗いてチョットデキルになろうと思っています。

まだまだ足りておらず飢えてもいるので、いろいろ殴って強くなりたいです。

]]>
Sat, 31 Dec 2016 00:00:00 +0000
https://backpaper0.github.io/2016/12/01/wife_peropero.html https://backpaper0.github.io/2016/12/01/wife_peropero.html <![CDATA[世界の中心で愛を叫んだうらがみ]]> 世界の中心で愛を叫んだうらがみ

これは 妻・夫を愛してるITエンジニア Advent Calendar 2016 の1日目です。

Twitterでのノリと勢いで作ったんですが、早い段階で埋まった上に その2 も作られて、なんだかんだですごく楽しみなAdvent Calendarになりました。

書くことは特に定めておらずフリースタイルとなっています。 みなさん、それぞれの形で惚気て頂ければと思います。

5周年

結婚 してからまる5年が経ちました。 特に激しいケンカもなく、のんびりと、ゆる〜く夫婦をやっております。

距離感

妻も私と同じくプログラマです。 だけど私と異なり、家でまでコードを書くようなプログラマではありません。 たぶん、まったくの別業界の人よりは私のようなプログラマに理解があり、それでいて私の趣味には干渉しないぐらいの距離感が心地良く感じています。

週末は二人で出かけることが多いですが、「今週末は勉強会に行きたい!」と言っても「良いよ」と返してくれて、別々に行動することもままあります。 もちろん、妻が友達と遊びたいと言ってきた場合は私も了承するので、お互いを縛ることなく楽しく過ごせています。

日常

朝は私が朝ごはんの準備をしている間に妻がお弁当の準備をしてくれます。 夜は先に帰った方が晩ごはんの準備をします。

仕事が終わって帰るときはLINEします。 たまに無意味なスタンプの応酬になります。 気付いたら最寄駅です。

寝るまでの間はお茶を飲みながらお互い好きに過ごします。 大抵、私はPCでコード書いたりTwitterしたりギター弾いたり、妻はスマホをいじったりテレビを見たりしています。 好きなことをしつつ、ときどき会話して、また好きなことをして、妻がドライヤーで髪を乾かし、また会話して。

お互いに別々のことをしていても同じ空間に居るのが大切で、結婚というのはそういうものなのかもなー、と思っています。

しみじみ

最近のデート

京都水族館、楽しかった。

まとめ

ただただ日常と、普段から思っていることを書いただけで、大して惚気られなかった気もしますがいかがでしたか?

結婚は人によって合う・合わないがあると思うので無条件ではオススメはしませんが、 私は結婚して良かったなーと思うことはあっても逆は一度もありません。

いつもそばに居てくれて、妻には感謝しているし、愛しています。

明日は @susumuis さんです。

]]>
Thu, 01 Dec 2016 00:00:00 +0000
https://backpaper0.github.io/2016/10/09/scala_ks.html https://backpaper0.github.io/2016/10/09/scala_ks.html <![CDATA[Scala関西Summit 2016へ参加・登壇したぞ! #scala_ks]]> Scala関西Summit 2016へ参加・登壇したぞ! #scala_ks

参加した!

で、セッション募集に応募したら採用されたので登壇もした!

セッションではScalaの表現力を支える仕様を解説して、仕組みが分かるとまた違った楽しさが見えてくるよねー、っていう思いを伝えたかったのでした。

レビューしてくれたがくぞさん、ありがとうございました!!!

私はJavaエンジニアなので懇親会ぼっちになるかなー、と少し思ってたんですが友人をはじめ、わいわいお話できて楽しかったです!

]]>
Sun, 09 Oct 2016 00:00:00 +0000
https://backpaper0.github.io/2016/10/01/unreachable_statements.html https://backpaper0.github.io/2016/10/01/unreachable_statements.html <![CDATA[セミコロンレスJavaで戻り値のあるメソッドを定義する(ただし返ってこない)の解説]]> セミコロンレスJavaで戻り値のあるメソッドを定義する(ただし返ってこない)の解説

これがなぜコンパイルを通るのか?という疑問を見かけたので解説してみます。

到達不能文

例えば while(false) { ... } とした時、この while ブロック内の文は絶対に実行されません。 このように絶対に実行されない文を 到達不能 と表現し、到達不能な文がある場合はコンパイルエラーになります。

次のような内容で Sample1.java を作成して javac してみてください。

public class Sample1 {
    public void whileSample() {
        while(false) {
            System.out.println("到達不能!");
        }
    }
}

次のようなエラーになります。

Sample1.java:3: エラー: この文に制御が移ることはありません
        while(false) {
                     ^
エラー1個

for 文でも同じ事ができます。

public class Sample2 {
    public void forSample() {
        for(;false;) {
            System.out.println("到達不能!");
        }
    }
}
Sample2.java:3: エラー: この文に制御が移ることはありません
        for(;false;) {
                     ^
エラー1個

try 文はエラーメッセージは違いますが、次のようコードは到達不能な文を含むのでコンパイルエラーになります。

public class Sample3 {
    public void trySample() {
        try {
            System.out.println("到達可能");
        } catch(RuntimeException e) {
            System.out.println("到達可能");
        } catch(IllegalArgumentException e) {
            System.out.println("到達不能!");
        }
    }
}
Sample3.java:7: エラー: 例外IllegalArgumentExceptionはすでに捕捉されています
        } catch(IllegalArgumentException e) {
          ^
エラー1個

特別扱いの if

ここまで whilefortry の到達不能な文を含むコードがコンパイルエラーになる例を見てきました。 if 文でも同じような事ができるかと思いきや、次のコードはコンパイルできてしまいます。

public class Sample4 {
    public void ifSample() {
        if(false) {
            System.out.println("到達不能!");
        }
    }
}

到達不能な文を含んだ if 文はどのようにコンパイルされたのか、 javap -c Sample4 して確認してみましょう。

Compiled from "Sample4.java"
public class Sample4 {
  public Sample4();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void ifSample();
    Code:
       0: return
}

ご覧の通り、 if 文が綺麗さっぱり消えていますね。

では、 if の条件式を true に変えてコンパイルして javap してみましょう。 (クラス名などは少し変えてあります)

Compiled from "Sample5.java"
public class Sample5 {
  public Sample5();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void ifSample();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String 到達可能
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

if 文の中身はありますが、条件を評価している部分がなくなりました。

if がこのように特別扱いになっているのは、次のようなコードを書けるようにするためです。

static final boolean DEBUG = true;

void sample() {
    if(DEBUG) { System.out.println("logging"); }
}

先述の特徴があるので、定数 DEBUGtrue ならロギングされるけども、 false ならロギングのコードがクラスファイルからも消える、という感じです。

return を書かずに戻り値のあるメソッドを定義する

return 文にはどうしてもセミコロンを書く必要があります。 それが故にセミコロンレスJavaでは戻り値のあるメソッドを定義することはできないと考えられていました。

私も「どのようにすればセミコロンを省略して return が書けるのか」と考えては挫折を繰り返してきましたが、 あるとき「セミコロンを省略して return が書けないなら、そもそも return を書かなければ良いのでは」と発想の転換をしてみました。

前半で述べた通り、到達不能な文を含むとコンパイルエラーになります。 逆に考えると、無限ループは到達可能なのでコンパイルを通るし、無限ループの後ろは到達不能なので何を書いてもコンパイルエラーになる、ということですね。

while(true) {
    //到達可能
}
//到達不能

これにより冒頭に記載したツイートの通り、セミコロンを書かずに戻り値のあるメソッドを定義できました。

public class Semicolonless {

    String get() {
        while (true) {
        }
    }
}

ちなみにセミコロンはともかく、 return を書かずに戻り値のあるメソッドを定義する方法としては例外を投げるというのもあります。 (例えば Thread.destroy()

戻り値のあるメソッドに return 文なんて要らんかったんや!!!

まとめ

  • 一見不可能に思えることでも考え方を変えることで解決する場合がある
  • とはいえ解決したところで役に立たないものもある
]]>
Sat, 01 Oct 2016 00:00:00 +0000
https://backpaper0.github.io/2016/09/11/githubwww.html https://backpaper0.github.io/2016/09/11/githubwww.html <![CDATA[GitHubで毎日草生やし続ける運動を終了する]]> GitHubで毎日草生やし続ける運動を終了する

GitHubで毎日草生やし続ける運動とは

GitHubはコミットしたりプルリクするとプロフィールページのタイルっぽいやつが緑になります。 緑になるから「草を生やす」って比喩してるんですけど、それを毎日続けて緑で埋め尽くそうというものです。

進捗

7月最後の週ぐらいに草だらけになりました。

Slackでわざわざ報告している図。

それからも毎日草生やしてたので400日ぐらいは継続できていると思います。

なぜやろうと思ったか

三十路になって「これからもコードを書いていられるのだろうか?(環境というよりは自分のモチベーション的な意味で)」と思うようになりました。 なので好きかどうか自分自身を試そうかなー、と。

やってみて得たもの

やる前に思ってたよりもずっと楽しんで続けられたので、やっぱり自分はコード書くの好きなんだな、と思えました。これは嬉しい。

あと、大したコードは書けてないですが、サンプルやらビルドスクリプトが溜まってきて何かと役立っています。

なぜやめようと思ったか

「継続」を目的としていたので、書きたいコードが思いつかない日はREADME.mdを修正するといったセコい草もたまには生やしていましたが、もうそうまでして草生やすのは面倒に思えてきました。

それに、読書のような草が生えない活動にももっと時間を使いたいなーと思い始めました。

当初の目標であった一年は継続できたので、やめるのにも未練は無いですし。 (でもなんか気持ちの整理的にやめることは記しておきたくてこのエントリを書いてる)

まとめ

  • コード書くのが好きだと再確認できて個人的には意義がある試みでした
  • 草生やし運動は人によって合う・合わないがあるので特別おすすめはしません
  • 一年後、プロフィールページが不毛の地と化していたら嘲笑ってください
]]>
Sun, 11 Sep 2016 00:00:00 +0000
https://backpaper0.github.io/2016/09/06/private_method.html https://backpaper0.github.io/2016/09/06/private_method.html <![CDATA[privateメソッドについての思いの変遷]]> privateメソッドについての思いの変遷

UnitTestでのprivateメソッドとの向き合い方 - ジムには乗りたい を読んで、自分のprivateメソッドに対する思いを書き殴りたくなったので書いてみました。

変遷

新人の頃の☃「private?メソッド?というのがあるのか。ふむ。ふむ……?」

新人ではなくなったが若手の頃の☃「メソッドが大きくなってきたな。privateメソッドで分割だ!」

若手とは言えなくなった頃の☃「privateメソッドのテストコードってどう書いたら良いんだ?リフレクションか?」

2、3年前の☃「privateメソッドは共通処理を切り出すためのもの。呼び出し元のpublicメソッドのテストコードで担保される」

最近の☃「privateメソッド スベテ コロス!!!」

解説

新人の頃は割愛。

次の若手の頃の話は、これは大きいメソッドを単にぶつ切りにして満足しちゃってた感じ。 臭いものに蓋してるだけで何の解決にもなっていませんでしたね、今から思うと。

それからprivateメソッドのテストコードについて悩みました。 どうすれば良いんだ?と。 悩んだ挙句protectedにしちゃたりしてましたが、これも誤魔化しですね。

で、しばらくして、呼び出し元のテストで担保できるじゃろ、と思うようになりました。 この頃にprivateメソッドは複数のpublicメソッドに共通する処理を切り出すものだ、と考えるようになったのでした。 逆に言うと、publicメソッドの一部を共通化する以外の目的でのprivateメソッド導入はダメだと考えるようになりました。

そんでもって最近は、なるべくprivateメソッドは導入したくないと思っています。

年々、小クラス主義になってるのかなー。 みたいな。

まとめ

冒頭で記載したエントリでは、

結局のところ、privateメソッドの詳細に踏み込んだテストが必要になった時は、設計に何か問題がある可能性が高いということなのだと思う。

と書かれていましたが、私はprivateメソッドを導入しようと思ったタイミングで設計に問題があるのでは?と疑ってみるようにしています。

]]>
Tue, 06 Sep 2016 00:00:00 +0000
https://backpaper0.github.io/2016/07/12/doma_tokyo.html https://backpaper0.github.io/2016/07/12/doma_tokyo.html <![CDATA[東京でDoma勉強会やったぞ!!! #doma_tokyo]]> 東京でDoma勉強会やったぞ!!! #doma_tokyo

Doma 作者の 中村さん 、 Domaヘビーユーザーの がくぞさん と一緒にDoma勉強会を東京で開催してきました!!!

スライド

私のスライドはこちら。 2本、話させて頂きました。 Domaの主要な機能を私なりに紹介した発表と、ドメインクラスで型最高!という発表です。

がくぞさんのスライドはこちら。 collect検索の活用方法、なるほど感すごかったです。 真似させてもらいます!

そして、中村さんのスライドはこちら。 Doma開発のスタンス、我々ユーザーとの向き合い方に感動しました!!

「LTやってよ!」ってさらっと無茶振りしたのに見事に応えてくれた 多田さんやんくさん もありがとうございました!

TLへのリアクション

内容については、他の方々のブログにお任せするとして、ここからは当日のTLから幾つかのツイートをピックアップして、リアクションしたいと思います。

これです!

通常のSELECT文をページネーションするクエリや、 悲観排他するクエリに変換する際にRDBMSの方言を吸収するためのものですね。

ページネーションはH2なら offsetlimit を使うけど、Oracleなら rownum を使うし、悲観排他はOracleなら for update を使うけど、SQLServerなら with(updlock) を使う、といった感じです。 この辺をDomaの中で吸収してくれるのが Dialect です。

そうです。 Domaは SelectOptionsoffsetlimit を指定する事ができます。 他にも悲観排他、それからカウント取得なんかの指定もできます。

これは私の誤りのようです。 Mavenプロジェクトなのか、Gradleプロジェクトなのか、そういった違いにもよるのかも知れませんが、少なくともMavenプロジェクトだと何も不都合なく使えるようです。 NetBeans、ごめんなさい。

Domaはエンティティのフィールドを直接見に行くのでアクセサメソッドは不要です。

仰る通り、共通項目だけを持つ基底クラスとなるエンティティを作る必要があります。 基底クラスがあればエンティティリスナーは1つで良いので、楽といえば楽です。

これは後日、サンプルを作ろうと思います。 (私のGitHubリポジトリを漁ったら既にあるかも知れませんが)

ページネーションは SelectOptionsoffsetlimit で指定します。 Stream 検索はあくまでも ResultSet.next してエンティティにマッピングする処理を Stream で表現しているだけです。

Stream を返すためではなく、他の事に心を込めような!!!

……と、冗談はさておき、元々はSpring Batchの ItemReader では ResultSet.next がメソッドをまたぐ必要があるため、それに対応しやすいように入れられた機能です。 通常は使う機会は無いと思います。

これも後日サンプルを書いてみようと思います。

Spring BootではないSpringでも使えます。 実装には doma-spring-boot-autoconfigure が参考になると思います。

Domaが必要とするのは DataSource だけですので、SpringでもJava EEでもJava SEでも、基本的にどこでも使えます。

使えます。 やってる事は単純で Connection.setAutoCommitConnection.commitConnection.rollback を組み合わせてトランザクションを行っているだけです。 あとは ThreadLocal を利用してトランザクションの期間中 Connection をスレッドに紐付けています。

LocalTransactionDataSource まわりのコードは小さいので、読んでみるのも楽しいですよ!

@Transactional はJava EEやSpringといったコンテナの機能で、Domaはサポートしていません。 Domaの範囲からは逸脱すると私は考えます。

とはいえ、例えばGuiceのような軽量コンテナであってもインターセプタの機能を有しているので、 Domaのローカルトランザクションと組み合わせて宣言的トランザクション機能を自作することは可能です。

これも後日サンプルをGuiceとDomaで書いてみますね。

シュンツさん、ありがとうございます!!!

ソッコーで実装されててわろた。

ですね、よくわかります。 (なのでわがまま言うつもりはありません)

確かに、ドメインクラスを推進するとクラス数は多くなりますが、型の恩恵を受けられるメリットの方が大きいと私は判断しています。

フレームワークや共通部品のような抽象的な層なら getValue へのアクセスを許可します。 テンプレートにドメインクラスを渡す場合はコンバータを書きます(例えばJAXBの XmlAdapter )。 その際、コンバータには getValue へのアクセスを許可します。

ありがとうございます。 本当に嬉しいお言葉です。 イベントを開催して良かった。

蛇足:DDDとの対比(個人的な見解)

ドメインクラスという名前のせいか、DDDに関係するのかな?といったツイートを見かけた事がありますが、DomaはDDD用のフレームワークではありません。 しかし、DDDで語られる各要素がDomaで言えば何なのかを考える事はできます。

個人的には次のように考えています。

  • Domaの「テーブルと1対1にマッピングするエンティティ」は、DDDの「エンティティ」に相当する
  • Domaの「検索結果にマッピングするエンティティ」と「ドメインクラス」は、DDDの「値オブジェクト」に相当する

懇親会について

今回、懇親会の企画はしていたのですが、参加者を募集しませんでした。 これは、本当に私の我儘でして、中村さん、がくぞさんと私自身がたくさん話したい!と強く思っていたので、広く募集はせずにスタッフと最後まで残って会場の現状復帰に付き合ってくれた方々数名だけとさせて頂きました。

我儘を通した甲斐があって、楽しい夕食の時間を過ごさせて頂きました。

イベント会場であるdotsについて

ほんまそれ。

最高でした! 勉強会を開催したい方には、会場候補におすすめします!

謝辞

このイベントを開催しようと思ったきっかけは、春に東京へ遊びに行った際、一緒に晩御飯を食べてくれる人を募集したらがくぞさんが来てくださったところから始まりました。 がくぞさんが「一緒にやりましょう」と言ってくださったので中村さんにもお声がけする勇気が出ました。 がくぞさん、本当にありがとうございました!

同じく、その晩御飯の席に参加してくださっており、会場としてdotsを挙げてくださった とーますさん にも感謝です! dotsに関する事のサポートや、懇親会のお店の手配をしてくださり、ありがたかったです。 晩御飯、美味しかったー!

それから、運営の手伝いを買って出てくださった多田さん、やんくさん、ありがとうございました! 無茶振りのLTにも応えてくださり、イベントがより華やかになりました!

そして、中村さん、突然お声がけしたにも関わらず登壇を快く引き受けてくださってありがとうございました! ずっと以前からお会いしたくて、でもきっかけが無くて、がくぞさんのおかげで勇気を出せて、お声がけして、ようやくお会いできました。 本当に、本当に嬉しかったです!!!

あと、友人(と表現させてください)の しょぼちむ、 うがさん、 てんてんさん、 ちっひー、 はすぬまさん、 @suzukijさん、 Denさん、 まめぴー、来てくださってありがとうございました! 中村さんの前での発表はこれでもか!!!ってぐらい緊張していたけど、みんなが居ると思えばこそ最後まで発表できた気がします。

シュンツさん、来てくださってありがとうございました! 参加登録者を見て、密かに「お会いできる!」とワクワクしていました。 お会いできて嬉しかったです!

最後になりますが、参加してくださった皆さん、本当にありがとうございました! 皆さんのおかげでイベントが賑やかになり、楽しく過ごす事ができました。

本当に楽しい、夢のような1日でした。

みんなで撮った集合写真は、MBPのデスクトップを飾っています。

]]>
Tue, 12 Jul 2016 00:00:00 +0000
https://backpaper0.github.io/2016/06/12/slider.html https://backpaper0.github.io/2016/06/12/slider.html <![CDATA[関Javaで使ってたスマホでスライドめくるやつ]]> 関Javaで使ってたスマホでスライドめくるやつ

今日は関Javaでした!

で、私も発表したんですが……

……というツイートを見て、気付いてくれた! そこに興味持ってくれて嬉しいぞ! という感じなので、これについて書きます。

スライドめくるやついいなー

いろんな人が発表しているのを見ていると、なんかリモコン的なやつとか指輪っぽいやつでリモートでスライドめくってて「いいなーアレ」と思っていました。

で、自分も発表する時に使いたいなと思ったんですけど、スマホがリモコン的に使えそうだし操作はページ送りできたら良いだけだし作るかー、と。

作ってみた

ざっくり言うと、Webアプリになっていまして、スマホのブラウザでページを開くとWebSocketで通信を開始し、コマンドを送ってページ送りを行っています。

使い方と仕組み

使い方と共にもうちょい詳しく書きます。

まずコードを clone して、ビルドします。

git clone https://github.com/backpaper0/slider.git

cd slider

gradlew build

すると build/libs/slider.jar が出力されます。

スライドを表示する端末でWebアプリを起動します。

java -jar build/libs/slider.jar

なお、サーバーにはUndertowを使っています。 また、mainメソッドの起動にはSpringBootを使っています。

Webアプリが起動するとスマホのブラウザからWebページを開く必要があるのですが、そのために私はスマホでテザリングをしています。 テザリングをして、 ipconfig とか ifconfig で端末のIPアドレスを確認したら http://<ipaddress>:8080/ へスマホでアクセスします。

画面はこんな感じ。

../../../_images/slider.png

LeftRight でそれぞれ左・右にページを送ります。 その際、スクリーンショットを撮ってブラウザに送ります。 スクリーンショットは Screenshot ボタンでも取得できます。 そして Presentation ですが、これは私が利用している remark というスライドツールのプレゼンテーションモードを切り替えるために使います。

これらの実現方法ですが、まず ScreenshotLeft のボタンを押すとWebSocketでサーバーにコマンドを送ります。 コマンドと言っても SCREENSHOTLEFT といった単純な文字列です。

サーバーではコマンドを受け取ると Robotクラス を利用して スクリーンショットを撮ったり「左ボタンを押す」というシステム入力イベントを発生させたり します。 これでスライドのページ送りができました。 なお、スクリーンショットはBase64エンコードしてData URIにしてブラウザに送っています。 ブラウザ側ではそれをそのままCanvasに書き出しています。

欲しいものは作ればいいや

仕組みとしては以上でして、まあ説明してみると大したことはしていないんですが、これまでプライベートコーディングではフレームワークを試したりサンプルコードばかり書き捨てていたので、改めて「欲しいと思ったものを自分でも作れるもんだなー」としみじみ思いました。 欲しけりゃ作ろ、と思うようになってきたのは、この数年で出会った何人かのエンジニアのお陰です。 名前を出すのは照れくさいので出しませんが、本当に尊敬しています。

まとめ

  • 作ってはみたものの、使う機会を逃しており今回が初の実践でしたが、なかなか上手く行って良かった!
  • 何人かの方に気付いて貰えて嬉しかった!(「機会があったら使わせて欲しい」とまで言ってくれた方も居た!)

使いながらもっと便利になるようにちまちまメンテしたい所存です。

]]>
Sun, 12 Jun 2016 00:00:00 +0000
https://backpaper0.github.io/2016/05/08/boxsing_and_cache.html https://backpaper0.github.io/2016/05/08/boxsing_and_cache.html <![CDATA[ボクシングとキャッシュ]]> ボクシングとキャッシュ

Integerのキャッシュ

int がボクシングされると java.lang.Integer になりますが、このとき Integer.valueOf(int) が使われます。 このことは次のようなボクシングされるコードを書いてコンパイルしてからjavapすればよく分かります。

public class IntBoxingSample {
    public Integer boxing(int i) {
        return i;
    }
}

javapしてみた結果は次の通り。

Compiled from "IntBoxingSample.java"
public class IntBoxingSample {
  public IntBoxingSample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.Integer boxing(int);
    Code:
       0: iload_1
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: areturn
}

で、この Integer.valueOf(int) ですが、 -128 から 127 までの範囲はキャッシュされます。

このことは 言語仕様の5.1.7Integer.valueOf(int)のJavadoc に書かれています。

でもって、OpenJDKのコードを見た感じ、 java.lang.Integer.IntegerCache.high というシステムプロパティでキャッシュする範囲を変更できそうです。

というわけで次のようなコードを書いてコンパイルして普通に実行すると false と表示されますが、 -Djava.lang.Integer.IntegerCache.high=1000 という風にシステムプロパティを設定して実行すると true と表示されます。

public class Sample {
    public static void main(String[] args) {
        Integer a = 1000;
        Integer b = 1000;
        System.out.println(a == b);
    }
}

実行結果は次の通り。

% java Sample
false
% java -Djava.lang.Integer.IntegerCache.high=1000 Sample
true

他のプリミティブも見てみた

ByteShortLongInteger と同じく -128 から 127 までキャッシュされていました。 ただしキャッシュの範囲は変更できません。

それから Character0 から 127 までキャッシュされていました。 ASCII文字コードの NUL から DEL ですね。

Boolean は定数 TRUEFALSE のどちらかを返すようになっています。

最後に FloatDouble ですが、どちらもキャッシュせず常にインスタンス化するようになっていました。 浮動小数点数なので -128.0 から 127.0 の間にも膨大な量のインスタンスを生成し得るので、まあ、そりゃキャッシュしないか、という感じ。

まとめ

以上のように普段は意識しないような部分でキャッシュしておりパフォーマンス向上を図っていたりしています。 こういったJDKの努力に感謝しつつ、今後も意識せずにコーディングしようと思います。

]]>
Sun, 08 May 2016 00:00:00 +0000
https://backpaper0.github.io/2016/03/24/tokyo.html https://backpaper0.github.io/2016/03/24/tokyo.html <![CDATA[東京でみんなと遊んできた]]> 東京でみんなと遊んできた

Elasticsearchハンズオンに参加したので ついでに東京まで行ってみんなと遊んできた!

せろてぃーたちとカラオケ行ったり、 がくぞさんと初めてお会いしたり、 しょぼちむぺろぺろしたり、 まーやさん・ちっひーは二日連続で遊んでくれたり、 とーますさんが集合時間を一時間勘違いしてたり、 まめぴーが3000万円の車を買おうとしていたり、 高校時代の友人が(私がRTするせいで)こざけさんのファンになっていたり、 楽しい休日を過ごさせてもらった!!!

あと、グラニースミスのアップルパイはあいかわらず最高だった。 東京に行ったら必ず食べたい一品。

../../../_images/grannysmith-applepie.jpg

みんなありがとう〜

楽しかった!!!

]]>
Thu, 24 Mar 2016 00:00:00 +0000
https://backpaper0.github.io/2016/03/24/elastichandson.html https://backpaper0.github.io/2016/03/24/elastichandson.html <![CDATA[Acroquest Technology社のElasticsearchハンズオンに行ってきた #elastichandson]]> Acroquest Technology社のElasticsearchハンズオンに行ってきた #elastichandson

Elasticsearch、まったく触ったこと無くてそろそろ触ろうかなーと思ってた矢先にハンズオンがあることを知って即申し込んで行ってきました。

ハンズオンの概要

Acroquest Technology社が業務で収集した実データをベースにした大量のデータを使ってLogstash/Elasticsearch/Kibanaのインストールから入門的な操作を行いました。

感想

レベル的には先述の通り、入門!って感じでしたが、丁寧な資料や実データをもとにして手を動かしたのもあって次の学習ステップを想像しやすくて良かったです。

個人的にはElasticsearchには興味があったんだけど、 LogstashとかKibanaとかナニモノなのかよく分からないし、 興味はあるけど自分のリソース注ぎ込みまくるほど(当時は)優先順位は高くなかったので、 そういった意味でも触れる機会を得られて助かりました。

その他

ステッカー、一通りもらいました!

素晴らしい!!!(いろんな意味で)

きつねさんのアイデアを元に、

  • 数値演算子を使わなくて済むように Random.nextInt を使用
  • new演算子を使わないためにstaticファクトリーメソッドがある SecureRandom を使用

といった事に気をつけて次のようなコードにしました。

//これはセミコロンを書いて良い普通のJava

//これで1〜100のIntStreamが手に入る
SecureRandom r = SecureRandom.getInstanceStrong();
IntStream stream = IntStream.generate(() -> r.nextInt(101))
                            .distinct().limit(101)
                            .sorted().skip(1);

ちなみに、私が考えついたのは次のようなコードでした。

//これはセミコロンを書いて良い普通のJava
Stream.of((ArrayList) Collectors.toList().supplier().get())
      .peek(list -> list.add(BigInteger.ONE))
      .map(list -> IntStream.generate(() ->
         Stream.of((BigInteger) list.get(0))
               .peek(a -> list.remove(0))
               .peek(a -> list.add(a.add(BigInteger.ONE)))
               .findFirst().get().intValue()))
      .findFirst().get();

簡単に言うと ArrayList から値を取り出して返し、その値に1足して、また ArrayList に格納する、を繰り返しています。

ArrayList の生成には Collectors.toList で返される Collectorsupplier を利用しました。 ぶっちゃけ Collectors.toList の実装に依存しており美しくないですね。

また、記号を抑えるためにraw型を使用していますが、そのせいでキャストが多発しており、これも美しくないです。

その点、きつねさんが提案してくれた方法はコードが美しく、より狂気があふれており素晴らしい!!!

というわけで、セミコロンなどレスJavaで書いたFizzBuzzは次のようになりました。

public class SemicolonlessFizzBuzz {

    public static void main(String... args) throws Exception {
        if (java.util.stream.Stream
            .of(java.security.SecureRandom.getInstanceStrong())
            .map(r -> java.util.stream.IntStream
                .generate(() -> r.nextInt(101))
                .distinct().limit(101).sorted().skip(1)
                .mapToObj(i -> java.util.Optional.of(java.math.BigInteger.valueOf(i)))
                .map(i -> i.filter(a -> a.mod(java.math.BigInteger.valueOf(15))
                    .equals(java.math.BigInteger.ZERO)).map(a ->
                                    String.valueOf((char) 0x46)
                            .concat(String.valueOf((char) 0x69))
                            .concat(String.valueOf((char) 0x7a))
                            .concat(String.valueOf((char) 0x7a))
                            .concat(String.valueOf((char) 0x42))
                            .concat(String.valueOf((char) 0x75))
                            .concat(String.valueOf((char) 0x7a))
                            .concat(String.valueOf((char) 0x7a)))
         .orElseGet(() -> i.filter(a -> a.mod(java.math.BigInteger.valueOf(3))
                    .equals(java.math.BigInteger.ZERO)).map(a ->
                                    String.valueOf((char) 0x46)
                            .concat(String.valueOf((char) 0x69))
                            .concat(String.valueOf((char) 0x7a))
                            .concat(String.valueOf((char) 0x7a)))
         .orElseGet(() -> i.filter(a -> a.mod(java.math.BigInteger.valueOf(5))
                    .equals(java.math.BigInteger.ZERO)).map(a ->
                                    String.valueOf((char) 0x42)
                            .concat(String.valueOf((char) 0x75))
                            .concat(String.valueOf((char) 0x7a))
                            .concat(String.valueOf((char) 0x7a)))
                .orElseGet(() -> i.get().toString()))))
            .collect(java.util.stream.Collectors.joining(String.valueOf((char) 0x20))))
            .peek(fizzbuzz -> System.out.println(fizzbuzz))
            .count() > 0) {}
    }
}
]]>
Wed, 09 Dec 2015 00:00:00 +0000
https://backpaper0.github.io/2015/12/05/semicolonless_java_enum_method.html https://backpaper0.github.io/2015/12/05/semicolonless_java_enum_method.html <![CDATA[セミコロンレスJava 8の新機能「enumにメソッド生やす」 #semicolonlessjava]]> セミコロンレスJava 8の新機能「enumにメソッド生やす」 #semicolonlessjava

これは セミコロンレスJava Advent Calendar 2015 の5日目です。

レガシーセミコロンレスJavaにおけるenumの制限

Java言語ではenumにメソッドを生やす事ができます。

//これはセミコロンを付けても良いありふれた普通のJavaコード
public enum Hoge {
    FOO, BAR, BAZ;

    public void println() {
        System.out.println(name());
    }
}

しかしメソッドを定義するためには一番最後に宣言した列挙定数(上記の例でいうと BAZ )の後ろにセミコロンをつける必要があります。

これは回避できない制約なのでセミコロンレスJavaではenumにメソッドは生やせませんでした。

セミコロンレスJava 8におけるenumの新機能

しかし、Java 8でインターフェースにデフォルトメソッドを持てるようになり、 その副次効果でセミコロンレスJava 8ではenumにメソッドを生やす事が出来るようになりました。

手順は簡単で、デフォルトメソッドを定義したインターフェースを用意してenumでそれをimplementsするだけです。

コード例を示します。

public enum Hoge implements Fuga {
   FOO, BAR, BAZ
}

public interface Fuga {
    default void println() {
        if (System.out.printf("%s%n", ((Hoge) this).name()) != null) {}
    }
}

enumの列挙定数や他のメソッドにアクセスしたい場合はキャストすればOKです。

まとめ

  • enumにメソッドを生やせるようになり、ますますセミコロンレスJavaが便利に!
]]>
Sat, 05 Dec 2015 00:00:00 +0000
https://backpaper0.github.io/2015/12/01/jjug_ccc_2015_fall.html https://backpaper0.github.io/2015/12/01/jjug_ccc_2015_fall.html <![CDATA[JJUG CCC 2015 Fallに行ってきた #jjug_ccc]]> JJUG CCC 2015 Fallに行ってきた #jjug_ccc

JAX-RSネタで登壇してきた

というわけで登壇させて頂きました。

ペース配分が甘くて最後のテストコードのパートをまるごと省略してしまい、聴講してくださった皆様には大変申し訳なく思っています。 おそらく関Javaで再演しますが、その際はしっかり最後まで話せるよう練習しておきます。

また、懇親会で「物足りなかった」というご意見を頂きました。 確かに、できる限りJAX-RSをご存知ない方にもJAX-RSを知って頂きたく、最大公約数的な内容にしたので物足りないと感じた方はいらっしゃるだろうなと思いました。

ですので次(JJUG CCCとは限りませんが)はもうちょっと濃い目の話しをしたいなと思っています。

私のセッションを選んでくださった聴講者の皆様、私のCfPを選んでくださったJJUGスタッフの皆様、資料レビューに付き合ってくれたほげメン・やんくさん、本当にありがとうございました!

まーやさん、たろうさんのコミュニティセッションでお話してきた

関Javaというコミュニティのスタッフをやっているという事でお声がけ頂いてお話してきました。

内容についてはまーやさんが素敵なレポートを書き上げてくださいました!

ここでは時間の都合で話せなかった事を記載しておきます。

苦労話 会場の確保

これは毎回悩みます。

テーマによってどの程度の集客が見込めるか考えて、その規模感に合った会場を探して、という感じで色々考えた挙句ばふぁさんを頼る、というのが最近のアレ。

本当に最近はばふぁさんを頼りすぎているのですが、いつも快く引き受けてくださって当日も笑顔で迎えてくださるのでもうホントなんというかマジ感謝ありがとうございます大好きラブ!って感じです。

抱えている課題(or 過去にあった課題) 調整力不足

運営には登壇者と日程調整して、会場を探して、懇親会のお店を探して、内容によっては当日までにリマインダメールを投げたり、 というような事をする調整力というかなんというか、そんな感じのプログラマの苦手領域っぽいスキルが求められます。

私も最初は……というかまあ今も苦労してはいますが、何度もやっているうちに慣れてきて前よりは上手くやれるようになりました。 そして仕事をする上でも以前よりも調整事ができるようになって業務を円滑に進められるようになったと思っています。

プログラミングの勉強会の運営をやって、プログラミングスキル以外のスキルがレベルアップした、という。 棚ぼた。

その他、個人的な思い

私はコミュニティ運営をしていない一般の参加者だった頃、運営をやっている人たちを神格化していたように思います。 あの人たちはすごい! 参加しかしていない自分よりも上の人だ! 殿上人だ! と。

でも自分が運営に関わってみると運営は神ではないし、「コミュニティ運営してる・してない」はエンジニアの優劣になんら関係無いという事を理解しました。 まあ当たり前っちゃ当たり前なんですが。 とはいえ勉強会って楽しいし、どうやって開催するのかその時は全然見当もつかなかったし、なんだかそう思っちゃってたんですよね。

まあ何が言いたいかというと「コミュニティ運営をしている・していない」は上下関係じゃなくて異なるレイヤーだということです。 やりたい人がやれば良いし、やってる人は楽しくてやってるだけなので運営に関われない事を負い目に感じる必要は無いんだよ、と。 そんな感じです。

私は(これは会場でも言いましたが)勉強会に参加してくださった方が書いた感想ブログを読んだり、 Twitterで「参加してよかった!」とツイートなさるのを見るのがすごく嬉しいので、まだしばらくはコミュニティ運営に関わって行きたいと思っています。

面白いセッションにお誘い頂いたまーやさん・たろうさん、一緒にパネラーをしたまみさん・ひつじさん、それから一緒に楽しんでくださった参加者の皆様、ありがとうございました! 楽しかったですね!

拝聴したセッション

ではここから拝聴したセッションについてざっくり記載します。

EF-3 Reactive Webアプリケーション – そしてSpring 5へ

まきさんのスピード感あふれるセッション(時間が足りない的な意味で)。 スピード感あふれていましたが、すごく分かりやすかったです。 (RxJavaをほんのり触った事があったからそれも幸いしたかな)

内容もすごく良かったのですが、時間が無いと仰っていたのにRod Johnsonとのツーショット写真やSpring Tシャツを嬉しそうに自慢するまきさんがかわいかったのも見所でした。

EF-5 これからのコンピューティングの変化とJava

きしださんの癒しボイス満載のセッション。

FPGAとかよく分からないんですが、自然な流れでJVM・Java言語の話になっていき、最後にはなるほど感が残る素敵なセッションでした。

でもやっぱりFPGAとかよく分かってない事に今気がついたので、きしださんのブログを読んで勉強します。

AB-6 【こっそり始める】Javaプログラマコーディングマイグレーション

資料レビューし合ったやんくさんのセッション。

自分が置かれている環境がアレな場合に、今居る環境を変える、もしくは別の環境に行く、 という二択のうち前者をとる話でした。

それも真正面から玉砕覚悟でぶつかっていくのではなく、隙を見つけてねじ込む搦め手をやってみようよ!っていう感じ。

個人的には「おれはやるだけやった、でもあいつらが分からず屋なんだ」と他責にしちゃうのは好きではないし、 過程よりも結果を残すのが大事だと思っているので、搦め手で確実に改善を狙うスタンスには好感を持ちました。

懇親会

そう長くない時間で色々あったけどまとめきれないので割愛! 楽しかった、とだけ言っておく!

色んな人に挨拶できたけど、しきれなかったので来春にリベンジします!

JJUG CCC 2015 Fall、楽しかった!!!

]]>
Tue, 01 Dec 2015 00:00:00 +0000
https://backpaper0.github.io/2015/11/15/javajok.html https://backpaper0.github.io/2015/11/15/javajok.html <![CDATA[関西Java女子部主催「Javaでwebアプリケーション入門」をお手伝いしてきました #javajok]]> 関西Java女子部主催「Javaでwebアプリケーション入門」をお手伝いしてきました #javajok

こんばんは! Eclipseへのインポート手順を解説していた @backpaper0 です!

表題の通り、Javaのハンズオンイベントをお手伝いしてきました!

会場は楽天株式会社大阪支社のカフェテリアをお借り致しました。 @bufferingsさん 、休日なのにいつも笑顔でお付き合いくださりありがとうございます!

なお、会場提供くださった楽天株式会社大阪支社では11月21日(土)に Rakuten Technology Conference 2015 というイベントのサテライトが行われるそうです。

参加してみて私が得たもの

私は講師側として参加しましたが、分かりやすく教えることの難しさを学べました。

また、自分が初心者だった頃に何が分からなかったか、何を分かりたかったか、どうやって分かるようになったか、 などを見つめ直す良いきっかけとなりました。

運営にお誘いくださった @aa7thさん 、 今回のイベント開催のきっかけとなった @ar_keyakiさん 、 それから今回ご参加頂いた皆さん、本当にありがとうございました!

Eclipseへのインポートについて

さて、本日はプロジェクトをEclipseへインポートする箇所で不手際があり参加者の皆さんにはご迷惑をお掛けし、申し訳ありませんでした。

もしご自宅で復習をなさる場合は、改めて https://github.com/javajok/simpletter からZIPファイルをダウンロードし、 gradlew eclipse を行ってインポートしてみてください。

なお、ダウンロードされるファイルの名前は「simpletter-master.zip」です。

APIについて

ハンズオンで使用したAPIのソースコードは次のURLにあります。

こちらもsimpletterと同じく、ZIPファイルをダウンロード・解凍して gradlew eclipse を行うことでEclipseにインポートする形式にできます。

余力がある方・興味がある方は宜しければこのAPIのソースコードも読んで、 色々といじってみてください。

最後に

私は基本的には詰まった時などのサポートに徹していました。

サポートの際はなるべく分かりやすく言葉を選び、そして「こうすれば出来る」だけでなく「なぜそうなるのか」も説明するよう心がけていました。 ですので、もし私のサポートがみなさんの理解の助けになれたのであれば、すごく嬉しいです。

それから、もしまた別のイベントでお会いする事があれば気軽にお声がけください。 今日の事を思い出しながらJavaでWebについてお話しましょう!

それでは繰り返しになりますが、本日は本当にありがとうございました! すごく楽しかったです!

]]>
Sun, 15 Nov 2015 00:00:00 +0000
https://backpaper0.github.io/2015/10/08/spring_boot_rich_banner.html https://backpaper0.github.io/2015/10/08/spring_boot_rich_banner.html <![CDATA[Spring Bootでカラフルなバナーを表示してみた]]> Spring Bootでカラフルなバナーを表示してみた

というわけでカラフルなバナーを表示するBannerクラスを書いてみました。

どうやってんのか

ターミナルの背景色を変更してスペースを2つ出力、を繰り返して絵を描いています。 スペースを2つ出力することで正方形になって良い感じにドット絵っぽくなります。

背景色を変えるには

ESC + '[48;05;' + 色のインデックス + 'm'

で出来ます。

次のGroovyコマンドを試してみてください。

groovy -e "System.out.write(0x1b);println('[48;05;20mHello, World!')"

背景色を元に戻すには

ESC + '[0m'

です。

それから、元画像はターミナルで出力できる色だけで構成されているわけではないので、 元画像から1ピクセルずつ色を読み込んでターミナルで出力できる256色の中から近い色を探して出力しています。

2つの色がどの程度近いかはカラーコードを三次元の座標に見立てて2つの色間の距離を求めて一番近いものを選んでいます。

int r = ((rgb1 >> 16) & 0xff) - ((rgb2 >> 16) & 0xff);
int g = ((rgb1 >> 8) & 0xff) - ((rgb2 >> 8) & 0xff);
int b = (rgb1 & 0xff) - (rgb2 & 0xff);
return (int) Math.sqrt(r * r + g * g + b * b);

概ねこんな感じです。

いろいろブート

うらがみブート。

../../../_images/uragami-boot.png

いろふブート。

../../../_images/irof-boot.png

ちむブートペロペロ(^ω^)

../../../_images/syobochim-boot-peropero.png

こざブート✌️( ・ㅂ・)و🍺

../../../_images/kozaboot.png

ブートくしーさん。

../../../_images/bootksy.png

やんくブート:q!

../../../_images/yank-boot_q.png
]]>
Thu, 08 Oct 2015 00:00:00 +0000
https://backpaper0.github.io/2015/08/25/serialized_lambda.html https://backpaper0.github.io/2015/08/25/serialized_lambda.html <![CDATA[シリアライザブルなラムダ式]]> シリアライザブルなラムダ式

ラムダ式は Serializable にできます。

//キャストしたり
Supplier<String> s = (Supplier<String> & Serializable) () -> "x";

//メソッドであれしたり
<T extends Supplier<String> & Serializable> void consume(T supplier) { ... }

で、シリアライズできるぞー、と思ってこんなコード書いて、

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.function.Supplier;

public class Sample {

    Supplier<String> get() {
        return (Supplier<String> & Serializable) () -> toString();
    }

    public static void main(String[] args) throws IOException {

        //↓シリアライザブルなサプライヤー
        Supplier<String> s = new Sample().get();

        //シリアライズしてみる
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(s);
        }
    }
}

実行すると、

Exception in thread "main" java.io.NotSerializableException: Sample
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at Sample.main(Sample.java:17)

例外です!

これはラムダ式で Sample クラスの toString メソッドを呼んでいるため Sample がキャプチャされますが、 Sample はSerializableでないため例外が出ます。

Supplier<String> get() {
    return (Supplier<String> & Serializable) () -> toString();
}

キャプチャというのはラムダ式内でラムダ式のスコープの外側の変数を参照した場合にラムダ式の実行環境に持ってくるっぽい感じのあれです。

キャプチャされているインスタンスは SerializedLambda から取ってこれます。 SerializedLambda はprivate finalな writeReplace メソッドで取ってこれます。 取ってこれるからと言ってカジュアルに呼んで良いメソッドではないです。 writeReplace については Serializable のJavadocに書いてあります。

こんな感じで SerializedLambda とキャプチャしたインスタンスを取ってこれました。

Supplier<String> s = new Sample().get();

Method m = s.getClass().getDeclaredMethod("writeReplace");
m.setAccessible(true);
SerializedLambda sl = (SerializedLambda) m.invoke(s);
for (int i = 0; i < sl.getCapturedArgCount(); i++) {
    System.out.println(sl.getCapturedArg(i));
}

この辺りをもっといじくりまわすと面白い事ができそうな気がしなくもないですね!

まとめもオチもない

とりあえず Sample クラスは Serializable をimplementsしましょう。

]]>
Tue, 25 Aug 2015 00:00:00 +0000
https://backpaper0.github.io/2015/07/30/gradle_plugin.html https://backpaper0.github.io/2015/07/30/gradle_plugin.html <![CDATA[Gradleプラグインを書いて公開しちゃった]]> Gradleプラグインを書いて公開しちゃった

きっかけ

JAX-RSでWebフォントなどの静的リソースを返すリソースメソッドを書いたんですが、 ETagを使ってキャッシュをアレして転送量を節約したくなりました。

で、ETagにはMD5ハッシュ値を使ってたんですが毎回ハッシュ値を計算するのはあほくさいので、 ビルド時に計算してファイルに保存しておく事にしました。

その際にGradleプラグインを書いて、 どうせならと思って https://plugins.gradle.org/ で公開する事にしました。

Gradleプラグインを書く

次の公式ドキュメント(の日本語訳)を参考にすればオッケー!

Gradleプラグインを公開する

How do I add my plugin to the plugin portal? の通りに進めていけばオッケー!

ざっくり書くと、 まず アカウントを作って 、 自分のページでAPI Keyを作って、 それを ~/.gradle/gradle.properties に書いて、 build.gradlePlugin Publishing Plugin の設定を書いて、 gradle publishPlugins で公開します。

Gradleプラグインを書くときに知ってて良かったこと

知ってて良かったことっていうかGroovyの文法なんですけど、 次のようなことを知ってたらわりとスムーズにプラグインを書けました。

  • アクセサメソッドはフィールドアクセスのように書ける。
    • 例えば foo.getBar()foo.bar と書ける。
    • そして foo.setBar(baz)foo.bar = baz と書ける。
  • メソッド呼び出しで最後の引数がクロージャだと括弧の外に出せる。 つまり foo(bar, { x -> ... })foo(bar) { x -> ... } と書けて組み込みの構文のようにできる。
  • 引数がMapなら foo(bar: "...", baz: 123) みたいに書ける。
  • 展開演算子。 ['hoge', 'foo', 'x'].collect { it.length() }['hoge', 'foo', 'x']*.length() と書ける。
  • leftShift という名前のメソッドは << と書ける。 タスクを定義するときの task hoge << { ... }Task.leftShiftメソッド です。

Gradleプラグインを書くにあたって参考にしたもの

最初に挙げた公式ドキュメントの日本語訳はもちろん参考にしましたが、 他に GradleのAPIドキュメント が参考になりました。

特に次のクラスのドキュメントをよく読んだ気がします。

それから Gradleのソースコード も参考になりました。 特に JavaPluginWarPlugin を参考にしました。

まとめ

Gradleプラグインは書くのも公開するのもお手軽っぽいので、 これを読んで良いなと思ったらチャレンジしてみてくれさい!

ちなみに、こちらが私が書いたプラグインですどうぞ!

]]>
Thu, 30 Jul 2015 00:00:00 +0000
https://backpaper0.github.io/2015/07/12/jsr310_and_lambda_handson.html https://backpaper0.github.io/2015/07/12/jsr310_and_lambda_handson.html <![CDATA[Java 8徹底再入門やった #kanjava]]> Java 8徹底再入門やった #kanjava

東京から @khasunuma さんを講師にお迎えして JSR 310: Date and Time API の解説をして頂きました。

またイベントの後半は @bitter_fox さんにラムダ式・Stream APIのハンズオンを行って頂きました。

会場は楽天株式会社大阪支社のカフェテリアをお借り致しました。 @bufferings さん、いつもご協力ありがとうございます。

お二人とも初歩的な部分から丁寧にお話・ハンズオンをして頂いたので参加された方々は Date and Time API と ラムダ式・Stream API の基礎を身に付けることが出来たのではないでしょうか。

はすぬまさん、きつねさん、本当にありがとうございました!

]]>
Sun, 12 Jul 2015 00:00:00 +0000
https://backpaper0.github.io/2015/06/29/grgit.html https://backpaper0.github.io/2015/06/29/grgit.html <![CDATA[Grgitでコミットのハッシュ値をファイルに書き出してwarに入れる]]> Grgitでコミットのハッシュ値をファイルに書き出してwarに入れる

今デプロイされてるwarはどのコミットから作ったんじゃろ? っていう疑問を解決するためのやつです。 Gradleでwarを作る前に Grgit というものを使ってコミットのハッシュ値をファイルに書き出しておいて warに入れてしまってついでにJAX-RSのリソースとしてあれしてしまいましょう、 という話。

build.gradle

まずGrgitを使う準備。 Gradleは普通にGroovyコードを書けて便利。

import org.ajoberstar.grgit.Grgit

ext.repo = Grgit.open(project.file('.'))

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath('org.ajoberstar:gradle-git:1.1.0')
    }
}

次にハッシュをファイルに書き出すタスクの定義とwarタスクとの依存関係の設定。 今回はwarファイル内の WEB-INF/classes/head にファイルがパッケージングされるようにしました。

task writeHeadCommitHash << {
    def file = new File(buildDir, 'git/head')
    file.parentFile.mkdirs()
    file.write(repo.head().id)
}

war.classpath new File(buildDir, 'git')

war.dependsOn writeHeadCommitHash

JAX-RSのリソースクラス

package javayou;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Named
@RequestScoped
@Path("head")
public class GitCommitHashResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getHead() {
        URL resource = getClass().getResource("/head");
        if (resource != null) {
            try {
                byte[] b = Files.readAllBytes(Paths.get(resource.toURI()));
                return new String(b);
            } catch (URISyntaxException | IOException ignored) {
            }
        }
        return "<none>";
    }
}

コーディング中にIDEから動かしたとかそういうときはファイルが無いから <none> って表示されるようにしています。

これで、このリソースにアクセスすればどのコミットから作られたwarなのかが分かるます。

動くコード例

動かして http://localhost:8080/java-you/api/head を開いてください。

]]>
Mon, 29 Jun 2015 00:00:00 +0000
https://backpaper0.github.io/2015/06/19/payaya_micro_cluster.html https://backpaper0.github.io/2015/06/19/payaya_micro_cluster.html <![CDATA[MacBook ProでPayara Microのクラスタリングを試そうとして躓いたけど出来た!]]> MacBook ProでPayara Microのクラスタリングを試そうとして躓いたけど出来た!

問題

Payara Micro Clustering を見ながらクラスタリング試そうと思ってあれしてみたんですが全然クラスタ組んでくれない!

Payara Microを2つ立ち上げた際の2つめのPayara Microのログの抜粋(クラスタ関連)がこれ。

[2015-06-19T04:03:01.749+0900] [Payara 4.1] [INFO] [] [com.hazelcast.cluster.impl.MulticastJoiner] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1434654181749] [levelValue: 800] [[
  [192.168.99.1]:5901 [dev] [3.4.2]


Members [1] {
        Member [192.168.99.1]:5901 this
}
]]

ご覧の通りメンバーがいない! ソロ活動だ!

クラスタ組んでくだされ〜(´;ω;`)

解決

などとTwitterで嘆いてたら かずひらさん が助けてくれた!

かずひらさん、それです!

というわけで次のようにすればいけた。

java -Djava.net.preferIPv4Stack=true -jar payara-micro-4.1.152.1.jar --deploy clusterjsp.war --port 8000

これでクラスタが組める! 組めるぞおおおおおおお! ( ゚∀゚)アハハ八八ノヽノヽノヽノ \ / \/ \

[2015-06-19T04:11:18.909+0900] [Payara 4.1] [INFO] [] [com.hazelcast.cluster.ClusterService] [tid: _ThreadID=45 _ThreadName=hz.glassfish-web.server.generic-operation.thread-1] [timeMillis: 1434654678909] [levelValue: 800] [[
  [192.168.99.1]:5900 [dev] [3.4.2]

Members [21] {
        Member [192.168.99.1]:5900 this
        Member [192.168.99.1]:5901
        Member [192.168.99.1]:5902
        Member [192.168.99.1]:5903
        Member [192.168.99.1]:5904
        Member [192.168.99.1]:5905
        Member [192.168.99.1]:5906
        Member [192.168.99.1]:5907
        Member [192.168.99.1]:5908
        Member [192.168.99.1]:5909
        Member [192.168.99.1]:5910
        Member [192.168.99.1]:5911
        Member [192.168.99.1]:5912
        Member [192.168.99.1]:5913
        Member [192.168.99.1]:5914
        Member [192.168.99.1]:5915
        Member [192.168.99.1]:5916
        Member [192.168.99.1]:5917
        Member [192.168.99.1]:5918
        Member [192.168.99.1]:5919
        Member [192.168.99.1]:5920
}
]]

無駄に21クラスタ!

ここまで書いて、 まだまだいけんじゃね?と思い、 調子に乗ってPMC48(Payara Micro Clusterデス)とかやろうとしたけど30パヤラ越えたあたりからMacさんが唸りだしたのでビビって止めてしまった。

謝辞

かずひらさんありがとうございました!!!

]]>
Fri, 19 Jun 2015 00:00:00 +0000
https://backpaper0.github.io/2015/06/07/git_archive.html https://backpaper0.github.io/2015/06/07/git_archive.html <![CDATA[Gitで管理してるソースをZIPにする]]> Gitで管理してるソースをZIPにする

小ネタです。 メモ的な。

GitHubにもpushしてない状態でちょっと人に見てもらいたいなー、 ってときにDropboxに置いてーとかやる事があるんですが、 そのときに.gitまで含めたくないし.gitignoreで無視してるファイルも含めたくないときに使うやつです。

git archive コマンドを使います。

git archive --format=zip HEAD > ../hoge.zip

--format に使えるフォーマットは git archive --list で確認できます。

]]>
Sun, 07 Jun 2015 00:00:00 +0000
https://backpaper0.github.io/2015/05/29/wildfly_auto_download.html https://backpaper0.github.io/2015/05/29/wildfly_auto_download.html <![CDATA[GradleでWildflyを自動でダウンロードしてからArquillianを実行する]]> GradleでWildflyを自動でダウンロードしてからArquillianを実行する

Java EEなアプリケーションのテストを回すのに Arquillian が便利なんですけど、 サンプル書くのに手動でアプリケーションサーバをダウンロードするのはちょい面倒なので、 Gradleにダウンロードしてもらうっていう話です。

アプリケーションサーバは Wildfly を使用します。

それではbuild.gradleをいじりましょー!

まずdependenciesにWildflyのアーカイブを追加します。

dependencies {
    archives "org.wildfly:wildfly-dist:$wildflyVersion@zip"
}

次にアーカイブをunzipするタスクを書きます。

task readyWildfly(type: Copy) {
    def wildflyZip = configurations.archives.find { it.name ==~ /wildfly.*/ }

    from zipTree(wildflyZip)
    into buildDir

    inputs.file wildflyZip
    outputs.upToDateWhen { new File(buildDir, "wildfly-$wildflyVersion").exists() }
}

既にunzipされたWildflyがあったらタスクをスキップしてほしいので inputs.fileoutputs.upToDateWhen でこちょこちょやっています。

最後にテストを実行する前にreadyWildflyタスクを実行するようにします。

test.dependsOn readyWildfly

あとは gradlew build するだけ。

ログはこんな感じ。

:compileJava
:processResources UP-TO-DATE
:classes
:war
:assemble
:readyWildfly
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 31.107 secs

This build could be faster, please consider using the Gradle Daemon: http://gradle.org/docs/2.4/userguide/gradle_daemon.html

testの前にreadyWildflyタスクが実行されていますね。 この時点で所定の場所へWildflyがunzipされています。

ではもう一度 gradlew build してみましょう。

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war UP-TO-DATE
:assemble UP-TO-DATE
:readyWildfly
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE

BUILD SUCCESSFUL

Total time: 10.617 secs

This build could be faster, please consider using the Gradle Daemon: http://gradle.org/docs/2.4/userguide/gradle_daemon.html

なんということでしょう! readyWildflyタスクがスキップされま……されてない!?

あれー???(´・_・`)

も、もう一度実行だ!

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war UP-TO-DATE
:assemble UP-TO-DATE
:readyWildfly UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE

BUILD SUCCESSFUL

Total time: 8.532 secs

This build could be faster, please consider using the Gradle Daemon: http://gradle.org/docs/2.4/userguide/gradle_daemon.html

なぜスキップされたし(・_・)

というわけで、なんかあと一歩で出来てない感がありますが、一番の目的である 「自動でWildflyの準備してArquillian走らせる」ってのは出来たので一旦これで良いやー。

]]>
Fri, 29 May 2015 00:00:00 +0000
https://backpaper0.github.io/2015/03/28/doma_listener_from_config.html https://backpaper0.github.io/2015/03/28/doma_listener_from_config.html <![CDATA[Doma 2.2.0からEntityListenerをDIコンテナから取得できるようになった #doma2]]> Doma 2.2.0からEntityListenerをDIコンテナから取得できるようになった #doma2

Doma 2.1.0までは EntityType 実装クラス(コンパイル時に自動生成されるクラス)のコンストラクタ内で単純にインスタンス化されていましたが、 Doma 2.2.0からは ConfiggetEntityListenerProvider というメソッドが追加され、 そのメソッドが返す EntityListenerProvider をカスタマイズすることで EntityListener のインスタンス取得をフックできるようになりました。

EntityListenerProviderEntityListener のインスタンスを取得する get メソッドを持っています。 EntityListenerProvider.get メソッドのデフォルト実装は次のようになっています。

default <ENTITY, LISTENER extends EntityListener<ENTITY>> LISTENER get(
        Class<LISTENER> listenerClass, Supplier<LISTENER> listenerSupplier) {
    return listenerSupplier.get();
}

ご覧のように単純に Supplier.get メソッドを実行しているだけです。

この EntityListenerProvider.get メソッドをオーバーライドしてDIコンテナから EntityListener のインスタンスを取得する例を書きます。 この例ではGuiceを使用しており Config 実装クラスと EntityListenerProvider 実装クラスもGuiceで管理しています。

まずは EntityListenerProvider 実装クラス。 Guiceの Injector をインジェクションしてそこから EntityListener のインスタンスを取得しています。

package sample;

import java.util.function.Supplier;

import javax.inject.Inject;

import org.seasar.doma.jdbc.EntityListenerProvider;
import org.seasar.doma.jdbc.entity.EntityListener;

import com.google.inject.Injector;

public class SampleEntityListenerProvider implements EntityListenerProvider {

    @Inject
    private Injector injector;

    @Override
    public <ENTITY, LISTENER extends EntityListener<ENTITY>> LISTENER get(
            Class<LISTENER> listenerClass, Supplier<LISTENER> listenerSupplier) {
        return injector.getInstance(listenerClass);
    }
}

次に Config 実装クラス。 EntityListenerProvider をインジェクションしてそのまま返しているだけです。

package sample;

import javax.inject.Inject;
import javax.sql.DataSource;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.EntityListenerProvider;
import org.seasar.doma.jdbc.dialect.Dialect;

public class SampleConfig implements Config {

    @Inject
    private EntityListenerProvider entityListenerProvider;

    @Inject
    private DataSource dataSource;

    @Inject
    private Dialect dialect;

    @Override
    public EntityListenerProvider getEntityListenerProvider() {
        return entityListenerProvider;
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }
}

Guice以外のDIコンテナでも似たような方法を取れるでしょう。 例えばCDIだと Injector ではなく BeanManager をインジェクションして BeanManager から EntityListener 実装クラスのインスタンスをルックアップすると良いと思います。 (CDI 1.1以降であれば CDI.current().select(listenerClass) でも良いと思います)

EntityListener をDIコンテナから取得できるようになると色々とインジェクションできるのも嬉しいですし、 インターセプターをかますことも出来たりしてさらに嬉しいですね!!!

]]>
Sat, 28 Mar 2015 00:00:00 +0000
https://backpaper0.github.io/2015/03/09/jersey_hk2_aop.html https://backpaper0.github.io/2015/03/09/jersey_hk2_aop.html <![CDATA[Jerseyでリソースメソッドをトランザクション境界にする]]> Jerseyでリソースメソッドをトランザクション境界にする

ちょろっと話題に出たので簡単にまとめました。

やりたいこと

リソースメソッドをトランザクション境界にしたい。

話題に出た方法

ContainerRequestFilterContainerResponseFilter を使って実現できないかなー、という話が出ましたが私は無理だと思っています。

ContainerRequestFilter はリソースメソッドの前に実行されるだけ、 ContainerResponseFilter はリソースメソッドの後に実行されるだけで try-catch-finally ができないからです。

パッと思いつく方法

GlasFishなどJava EE 7準拠のアプリケーションサーバで動かすのであればリソースクラスを CDI管理ビーンにして @Transactional で注釈すればそれだけでリソースメソッドがトランザクション境界になります。

Tomcat + Jerseyぐらいの構成で実現する方法

CDI使ってない、っていうかTomcatで動かしてる、つーかDIコンテナ使ってない!!!という場合はどうすれば良いのか?

Jerseyは内部的に HK2 というDIコンテナを使っています。 このHK2のAOP機能を利用してリソースクラスにインテーセプターを適用して リソースメソッドをトランザクション境界にする方法が取れます。

ザクッとサンプル書いてみたので詳細はコードを読んでください。

まとめ

  • JAX-RSの仕様ではサーブレットフィルタのような try-catch-finally ができるポイントが無い
  • CDIを併用すれば何とでもなる
  • Jerseyなら実装に依存するけどHK2を使うことで何とかなる
]]>
Mon, 09 Mar 2015 00:00:00 +0000
https://backpaper0.github.io/2015/03/08/spring_boot_camp.html https://backpaper0.github.io/2015/03/08/spring_boot_camp.html <![CDATA[Spring Bootキャンプをやった #kanjava_sbc]]> Spring Bootキャンプをやった #kanjava_sbc

@making さんを講師にお迎えしてSpring Bootを土台にしてプログラミングを楽しむハンズオンを開催しました。

会場は楽天株式会社大阪支社のカフェテリアをお借り致しました。 @bufferings さん、ご協力ありがとうございました。

今回のハンズオンではカメラで撮った写真をSpring MVCに投げてOpenCVで画像変換をしました。 変換部分はJMSで非同期処理しクライアントとサーバー間はSTOMPというプロトコルをWebSocket上で利用して通信しました。

Spring Bootの関係無さ感すごい!!!

しかし、このように色々な技術を使ったWebサービスのハンズオンを特にややこしい設定もせず 約2時間半で進める事ができたのはいろいろと自動で設定を宜しくやってくれるSpring Bootがあってこそだと思いました。

楽しいハンズオンを行って頂いて@makingさんには本当に感謝です!!!

ハンズオンの様子

ちなみに

東京でも開催されるっぽいですよ!

おまけ

Spring Boot + Jersey。 懇親会で話題に出たやつです。 前にちょこっとやりました。

]]>
Sun, 08 Mar 2015 00:00:00 +0000
https://backpaper0.github.io/2015/03/05/io.html https://backpaper0.github.io/2015/03/05/io.html <![CDATA[Reader/Writer/InputStream/OutputStream]]> Reader/Writer/InputStream/OutputStream

少し話題に出たのでファイル読み書きなどでよく使う感じのアレをアレしたいと思います。

まず、

  • InputStream / OutputStream はバイナリデータのストリームです。 byte[] で読み書きします。
  • Reader / Writer はテキストデータのストリームです。 char[] で読み書きします。
  • ストリームというのは byte[]char[] を使って少しずつデータを読み込んだり書き出したりするためのものです。

というのが基本になります。

ファイルの読み書き

テキストファイルを読み込む

Path path = Paths.get("path/to/file");
try (BufferedReader in = Files.newBufferedReader(path)) {
    //読み込み処理
}

ファイルはUTF-8で読み込まれます。

UTF-8以外のファイルを読み込む場合は第二引数に Charset を渡します。

try (BufferedReader in = Files.newBufferedReader(path, Charset.forName("Windows-31J"))) {
    String line;
    while(null != (line = in.readLine())) {
        //読み込み処理
    }
}

バイナリファイルを読み込む

try (InputStream in = new BufferedInputStream(Files.newInputStream(path))) {
    byte[] b = new byte[1000];
    int i;
    while (-1 != (i = in.read(b))) {
        //読み込み処理
    }
}

Files.newInputStream はバッファリングされないので巨大なファイルやたくさんファイルを扱う処理だと遅いと思います。 基本的には BufferedInputStream でラップする方が良いかとー。

テキストファイルを書き出す

try (BufferedWriter out = Files.newBufferedWriter(path)) {
    //書き出し処理
}

バイナリファイルを書き出す

try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(path))) {
    //書き出し処理
}

Files.newInputStream と同じく Files.newOutputStream もバッファリングされません。

オンメモリで扱う

Writer を渡したらそこにもろもろ書き出してくれるライブラリがあるんだけど わざわざファイルに書き出すんじゃなくてオンメモリで処理して String で結果を取りたいんや! というような場合には StringWriter を使います。

StringWriter out = new StringWriter();
library.writeTo(out);
String result = out.toString();

Reader / InputStream / OutputStream にもそれぞれオンメモリで使用するためのクラスがあります。

  • StringReaderString を読み込める Reader
  • StringWriterString へ書き出せる Writer
  • ByteArrayInputStreambyte[] を読み込める InputStream
  • ByteArrayOutputStreambyte[] へ書き出せる OutputStream

InputStreamをReaderへ/OutputStreamをWriterへ変換する

それぞれ InputStreamReaderOutputStreamWriter を使って変換できます。

InputStream in = ...
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);

OutputStream out = ...
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);

第二引数に Charset を渡していますが、何も渡さない場合はデフォルトエンコーディングが使用されるので注意が必要です。 デフォルトエンコーディングとはシステムプロパティ file.encoding で取得できるものです。 変更したい場合は次のようにJava起動時にオプションを設定します。

java -Dfile.encoding=UTF-8 com.example.MainClass

ZIPファイルを読み込む/書き出す

ZIPファイルの読み書きには ZipInputStreamZipOutputStream が使えます。

InputStream in = ...
try(ZipInputStream zin = new ZipInputStream(in, StandardCharsets.UTF_8)) {
    ZipEntry zipEntry;
    while (null != (zipEntry = zin.getNextEntry())) {
        byte[] b = new byte[1000];
        int i;
        while (-1 != (i = zin.read(b))) {
            //読み込み処理
        }
    }
}
OutputStream out = ...
try(ZipOutputStream zout = new ZipOutputStream(out, StandardCharsets.UTF_8)) {
    ZipEntry zipEntry = new ZipEntry("hoge.txt");
    zout.putNextEntry(zipEntry);
    byte[] b = ...
    zout.write(b);
    zout.closeEntry();
}

ファイルのコピー、移動をする

Files を使います。

Path src = ...
Path dest = ...

Files.copy(src, dest);

Files.move(src, dest);

Channel

Reader / Writer / InputStream / OutputStream の他に Channel というものもありますが Channel が必要になるライブラリには ほぼ出会った事がないので覚えなくても生きて行けると思います。

おまけ

テキストファイルの読み込みには Files.newBufferedReader を使うと書きましたが Java 6までは FileReader を使って次のようにファイル読み込みをしていました。

File file = new File("path/to/file");
Reader in = new FileReader(file);
try {
    //読み込み処理
} finally {
    in.close();
}

Charset を渡さずに FileReader をインスタンス化していますが、 この場合はデフォルトエンコーディングが使われていました。

しかも FileReader には Charset を受け取るコンストラクタは用意されていません。 ではデフォルトエンコーディング以外でファイルを読み込みたい場合はどうするのか?

その場合は、

  1. FileInputStream でファイルを開いて
  2. InputStreamReaderCharset を指定しつつラップする

という方法をとっていました。

File file = new File("path/to/file");
Reader in = new InputStreamReader(new FileInputStream(file), Charset.forName("iso-2022-jp"));
try {
    //読み込み処理
} finally {
    in.close();
}

そういう訳で java.ioCharset を受け取らない場合はデフォルトエンコーディング、 java.nio.fileCharset を受け取らない場合はUTF-8が使われる、という感じです。

デフォルトエンコーディングは環境によって変わるので java.nio.file を使っておくのが安全だと思います。

]]>
Thu, 05 Mar 2015 00:00:00 +0000
https://backpaper0.github.io/2015/02/28/junit_4_12_test_rule.html https://backpaper0.github.io/2015/02/28/junit_4_12_test_rule.html <![CDATA[JUnit 4.12から入ったTestRuleを軽く見てみる]]> JUnit 4.12から入ったTestRuleを軽く見てみる

DisableOnDebug

DisableOnDebug 他の TestRule をラップして、 デバッグ実行されているときのみラップした TestRule を適用します。

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;

public class HogeTest {

    @Rule
    public TestRule timeout = new DisableOnDebug(Timeout.seconds(1)); //1秒以上かかったら失敗とみなす

    @Test
    public void testHoge() throws Exception {
        //test code
    }
}

こんな感じで Timeout と組み合わせる事が多い気がします。

コマンドライン引数に次のいずれかが含まれていればデバッグ実行されていると判断するようです。

  • -Xdebug
  • -agentlib:jdwp

デバッグ実行かどうかの判断は DisableOnDebug.isDebugging メソッドをオーバーライドすればカスタマイズできます。

Stopwatch

Stopwatch はテスト実行にかかった時間を System.nanoTime メソッドで計測します。

import java.util.logging.Logger;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Stopwatch;
import org.junit.runner.Description;

public class FugaTest {

    private static Logger logger = Logger.getLogger(FugaTest.class.getName());

    @Rule
    public Stopwatch stopwatch = new Stopwatch() {
        @Override
        protected void succeeded(long nanos, Description description) {
            logger.info(() -> String.format("テストの実行に%,dナノ秒かかった", nanos));
        }
    };

    @Test
    public void test() throws Exception {
        //test code
    }
}

ロギング目的に使うのが多そうです。

]]>
Sat, 28 Feb 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/24/git_rebase_merge.html https://backpaper0.github.io/2015/01/24/git_rebase_merge.html <![CDATA[Gitでブランチを統合する方法]]> Gitでブランチを統合する方法

こういうコミットを重ねたブランチを、

../../../_images/git_rebase_merge_1.png

どういう方法でmasterに統合すると嬉しい派なのか?っていう小ネタです。

マージする

../../../_images/git_rebase_merge_2.png
git checkout master
git merge other -m "Merge branch 'other'"
git branch -d other

操作が分かりやすい感じがする。

リベースする

../../../_images/git_rebase_merge_3.png
git checkout master
git rebase master other
git checkout master
git merge other
git branch -d other

コミットが一本化する。

リベースしてからマージする

../../../_images/git_rebase_merge_4.png
git checkout master
git rebase master other
git checkout master
git merge other --no-ff -m "Merge branch 'other'"
git branch -d other

コミットが一本化しつつブランチ単位の作業を把握しやすい。

まとめ

私はリベースしてからマージする派!

おまけ

最初に提示したコミットを作るスクリプト。

#!/bin/sh
rm -fr .git *.txt .gitignore
git init
echo init.sh>.gitignore && git add .gitignore && git commit -m "Initial Commit"
echo b>b.txt && git add b.txt && git commit -m "master 1"
git branch other
echo c>c.txt && git add c.txt && git commit -m "master 2"
echo d>d.txt && git add d.txt && git commit -m "master 3"
git checkout other
echo e>e.txt && git add e.txt && git commit -m "other 1"
echo f>f.txt && git add f.txt && git commit -m "other 2"
git checkout master
]]>
Sat, 24 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/18/getting_started_libgdx.html https://backpaper0.github.io/2015/01/18/getting_started_libgdx.html <![CDATA[libGDXプロジェクトをセットアップする #libgdx]]> libGDXプロジェクトをセットアップする #libgdx

libGDXはJavaのゲームフレームワークで、デスクトップ・Android・iOS・HTML5用にビルドできるフレームワークです。 どのプラットフォームであっても起動用のクラス以外のほとんどのコードを共有できるすごいやつです。

今回はlibGDXを使ったプロジェクトのセットアップ方法を書いていこうと思います。

セットアップツールによるプロジェクトの生成

ダウンロードページ で “Download Setup App” をクリックして gdx-seup.jar をダウンロードしてください。

ダウンロードできたらダブルクリック、もしくは次のコマンドで実行してください。

java -jar gdx-setup.jar
../../../_images/gdx-setup-screenshot.png

各項目を編集します。

Name プロジェクトの名前。Androidプロジェクトはres/values/string.xmlのapp_nameにも使用される。
Package 出力されるサンプルコードのパッケージ。AndroidManifest.xmlに書かれるpackageにも使用される。
Game class ApplicationListener 実装クラス。libGDXにおける起点となるクラス。
Destination プロジェクトの出力先ディレクトリ。
Android SDK Android SDKのパス。Android用のビルドをする場合に必要。

“LibGDX Version” は最新のものが選択されているはずなので、そのままで。

“Sub Projects” はすべてチェックされていると思います。 不要になればその時点で取り除けば良いし個別にビルドもできるので、これもそのままで。

“Extensions” は “Box2d” のみがチェックされていると思います。 ここについては詳しい解説が出来るほどの知識がありません。 誰か教えてください! これもそのままでいきましょう。

以上の状態で “Generate” を押してください。

初回はGradleや依存JARのダウンロードが行われるので時間がかかります。 お茶でも飲んでお待ちください。

次のようなログが出ると完了です。

BUILD SUCCESSFUL

Total time: 42.551 secs
Done!
To import in Eclipse: File -> Import -> Gradle -> Gradle Project
To import to Intellij IDEA: File -> Import -> build.gradle
To import to NetBeans: File -> Open Project...

Note

プロジェクトのビルドには Gradle ( 日本語ドキュメント )が使われていますが、 gdx-setup.jarが設定済みのbuild.gradleを出力してくれるのでGradleに詳しくなくても大丈夫です。

寄り道:Gitでバージョン管理を始める

今後の事を考えてGitでのバージョン管理を始めておきましょう。

セットアップツールが .gitignore も出力してくれているのでややこしいことは何も考えずにバージョン管理を始められます。

git init
git add .
git commit -m "Initial commit"

Eclipseへのインポート

セットアップのログを見た感じだとEclipseにGradleプラグインが入っているとそのままインポート出来そうですね。

私はGradleプラグインが入っていないEclipseを使っているので、その場合のインポート方法を書きます。

まずGradleでコマンドを実行します。

gradlew eclipse

次にEclipseのメニューから File ‣ Import ‣ General ‣ Existing Projects into Workspace を選択します。 それから “Select root directory” にプロジェクトのパスを入力してください。

Note

プロジェクトのパスをコピーするときはMacなら次のコマンドを使うとクリップボードに格納されて便利です。

pwd|pbcopy

Windowsなら次のコマンドで同じ事ができます。

cd|clip

インポートするプロジェクトは code と desktop だけで良いでしょう。 基本的にはデスクトップで開発してたまに実機確認という感じで進められると思います。

インポートできたら(これが面倒なのですが)desktopプロジェクトにあるassetsディレクトリをクラスパスに加えてください。

手っ取り早い方法はassetsディレクトリで右クリックして Build Path ‣ Use as Source Folder です。

もしくは、プロジェクトのプロパティを開いて Java Build Path ‣ Libraries と辿って “Add Class Folder” を押してassetsディレクトリを指定してください。

IntelliJ IDEAへのインポート

私はIntelliJ IDEA分からんのですが、セットアップのログに書かれているように File ‣ Import ‣ build.gradle をやってみたところインポートできたっぽいです。

実行する

desktopプロジェクトの src/main/java/yourpackage/DesktopLauncher.java を実行してください。 ( yourpackage はセットアップ時に設定したパッケージです。 適宜読み替えてください。)

結び

というわけでlibGDXプロジェクトのセットアップ方法を記載してみました。

libGDXはAndroidアプリであってもデスクトップ中心で開発でき、 コードの殆どを共有できるのがすごくて嬉しくてお気に入りです。

願わくばもっともっとlibGDXユーザーが増えますように!

]]>
Sun, 18 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/17/spring_boot_jsr330.html https://backpaper0.github.io/2015/01/17/spring_boot_jsr330.html <![CDATA[Spring Bootのサンプルで使ってるアノテーションをJSR 330のものにした]]> Spring Bootのサンプルで使ってるアノテーションをJSR 330のものにした

最近Spring Bootで遊んでいます。

今回はSpringのアノテーションである @Component@Autowired をJSR 330のアノテーションである @Named@Inject に変更してみました。

ソースコードは https://github.com/backpaper0/spring_boot_sample です。

tagは https://github.com/backpaper0/spring_boot_sample/releases/tag/jsr330 です。

本題

特に書くことない。 普通に置き換えたら普通に動いたので。

ただしスコープのアノテーションはSpringのままです。 セッションスコープのクラスに付けたアノテーションを @SessionScoped に変更したかったのですがJSR 330ではなくCDIのアノテーションのためどうしようもなかったというかなんというか。

まあいいか。

まとめ

JSR 330のアノテーションの方が見慣れていて個人的には良い。

]]>
Sat, 17 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/16/jersey2_15_cdi.html https://backpaper0.github.io/2015/01/16/jersey2_15_cdi.html <![CDATA[Jersey 2.15のCDI統合を試す]]> Jersey 2.15のCDI統合を試す

Jersey 2.15でCDIとの統合機能が変更されたようです。

ざっくり読むと、これまではJava EEコンテナ(ていうかGlassFish?)との統合に注力してたけど 2.15からはJava EE環境じゃなくても統合できまっせ!という感じっぽいです。

試した

ハローワールドな、なんの役にも立たないWeb APIを作って試してみました。

メインクラスはこんな感じ。

package example;

import java.net.URI;

import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.jboss.weld.environment.se.Weld;

import com.sun.net.httpserver.HttpServer;

public class App {

    public static void main(String[] args) {
        Weld weld = new Weld();
        weld.initialize();
        URI uri = URI.create("http://localhost:8080/");
        ResourceConfig config = new ResourceConfig(HelloResource.class);
        HttpServer server = JdkHttpServerFactory.createHttpServer(uri, config);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            server.stop(0);
            weld.shutdown();
        }));

        System.out.println("http://localhost:8080/hello?name=YourName");
    }
}

ご覧の通りです。 普通にWeldを動かして、Jerseyを動かしてるだけです。 簡単です。

pom.xmlのdependencyはこんな感じ。

<dependency>
  <groupId>org.glassfish.jersey.ext.cdi</groupId>
  <artifactId>jersey-weld2-se</artifactId>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.ext.cdi</groupId>
  <artifactId>jersey-cdi1x</artifactId>
</dependency>

所感

私はJava EEは好きですがJava SEでも簡単に動くようになるのは嬉しいのでこの変更は嬉しいです!

アホっぽい感想ですが、まあそんな感じでー。

]]>
Fri, 16 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/15/spring_boot_gradle.html https://backpaper0.github.io/2015/01/15/spring_boot_gradle.html <![CDATA[Spring BootのサンプルをGradle化した、けども……]]> Spring BootのサンプルをGradle化した、けども……

最近Spring Bootで遊んでいます。

今回はMavenでビルドされているサンプルをGradle化しました。

ソースコードは https://github.com/backpaper0/spring_boot_sample です。

tagは https://github.com/backpaper0/spring_boot_sample/releases/tag/gradle です。

本題

まず、おもむろにgradle initしました。

gradle init

すでにpom.xmlがあるので依存関係とか色々よろしくやってくれたbuild.gradleが出力されました。

apply plugin: 'java'
apply plugin: 'maven'

group = 'sample'
version = '1.0-SNAPSHOT'

description = """spring-boot-sample"""

sourceCompatibility = 1.8
targetCompatibility = 1.8



repositories {

     maven { url "http://repo.maven.apache.org/maven2" }
}
dependencies {
    compile group: 'org.twitter4j', name: 'twitter4j-core', version:'4.0.2'
    compile(group: 'org.springframework.boot', name: 'spring-boot-starter-jersey', version:'1.2.1.RELEASE') {
exclude(module: 'spring-webmvc')
    }
    compile(group: 'org.glassfish.jersey.ext', name: 'jersey-mvc', version:'2.14') {
exclude(module: 'servlet-api')
    }
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version:'1.2.1.RELEASE'
    testCompile(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version:'1.2.1.RELEASE') {
exclude(module: 'commons-logging')
    }
    testCompile group: 'junit', name: 'junit', version:'4.12'
}

あとはSpring Bootのリファレンスの 10.1.2 Gradle installation を参考にしてちょこちょこっと編集しました。

buildscript {
  repositories {
    jcenter()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }
  }
  dependencies {
    //ここで拡張プロパティspringBootVersionは参照できひんの?_(:3」∠)_
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.1.RELEASE")
  }
}

apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: 'eclipse'
apply plugin: 'idea'

group = 'sample'
version = '1.0-SNAPSHOT'

sourceCompatibility = 1.8
targetCompatibility = 1.8

ext {
  springBootVersion = '1.2.1.RELEASE'
}

repositories {
  jcenter()
  maven { url "http://repo.spring.io/snapshot" }
  maven { url "http://repo.spring.io/milestone" }
}

dependencies {
  compile 'org.twitter4j:twitter4j-core:4.0.2'
  compile ("org.springframework.boot:spring-boot-starter-jersey:$springBootVersion") {
    exclude(module: 'spring-webmvc')
  }
  compile ('org.glassfish.jersey.ext:jersey-mvc:2.14') {
    exclude(module: 'servlet-api')
  }
  compile "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
  testCompile ("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
    exclude(module: 'commons-logging')
  }
  testCompile 'junit:junit:4.12'
}

知りたいこと

build.gradleにも書いたけどbuildscriptのブロック内で拡張プロパティspringBootVersionを参照できないのでしょうか? (試しに使ってみたらビルド失敗した。。。)

教えてくださいお願いしますお願いします(他力本願)。

早速解決しました!

ありがとうございます!

修正してコミットしました!

まとめ

Gradle化すげえ簡単だった。

]]>
Thu, 15 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/14/spring_boot_jersey.html https://backpaper0.github.io/2015/01/14/spring_boot_jersey.html <![CDATA[Spring BootのサンプルをJAX-RSにしてみた]]> Spring BootのサンプルをJAX-RSにしてみた

先日写経したSpring BootのサンプルがSpring MVCで書かれていたのでJAX-RS、 というかJersey MVCにしてみました。

ソースコードは先日と同じ場所に置いてあります。

tag作りました。

やったこと

まず Spring Bootのドキュメント を参考にしてpom.xmlの編集とJerseyConfigクラスを作成しました。

次に Jerseyのリファレンス を参考にしてTemplateProcessorの実装クラスを作成しました。

そして各ControllerクラスをSpring MVC仕様からJAX-RS仕様に変更しました。 詳細書くのは面倒なのでコミット見てください。

例外出た

一通りコードを書いて、IDEから動かしたら起動時に例外が出ました。

java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[]]
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:917)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:868)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1399)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
    ... 6 common frames omitted
Caused by: java.lang.NoSuchMethodError: javax.servlet.ServletContext.addFilter(Ljava/lang/String;Ljavax/servlet/Filter;)Ljavax/servlet/FilterRegistration$Dynamic;
    at org.springframework.boot.context.embedded.FilterRegistrationBean.onStartup(FilterRegistrationBean.java:250)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.selfInitialize(EmbeddedWebApplicationContext.java:222)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.access$000(EmbeddedWebApplicationContext.java:84)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext$1.onStartup(EmbeddedWebApplicationContext.java:206)
    at org.springframework.boot.context.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:54)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5185)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    ... 6 common frames omitted

なんかサーブレットAPIが古いようです。

十中八九Jersey関連が持ってきたんだろうな、と思いつつ、調査しました。 maven-dependency-plugin の treeゴールを使えば依存関係を一覧できます。 -l オプションでログをファイルに書き出してエディタで検索するのが楽だと思います。

mvn -l log.txt dependency:tree -Dscope=test

Mavenを実行すると次のようなログが書き出されました。

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-sample 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.9:tree (default-cli) @ spring-boot-sample ---
[INFO] sample:spring-boot-sample:jar:1.0-SNAPSHOT
[INFO] +- org.twitter4j:twitter4j-core:jar:4.0.2:compile
[INFO] +- org.springframework.boot:spring-boot-starter-jersey:jar:1.2.1.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.2.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.2.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.2.1.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.2.1.RELEASE:compile
[INFO] |  |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.8:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.8:compile
[INFO] |  |  |  +- org.slf4j:log4j-over-slf4j:jar:1.7.8:compile
[INFO] |  |  |  \- ch.qos.logback:logback-classic:jar:1.1.2:compile
[INFO] |  |  |     \- ch.qos.logback:logback-core:jar:1.1.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.14:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.2.1.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.0.15:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.0.15:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:8.0.15:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.0.15:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.4.4:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.4:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.4.4:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.0.0:compile
[INFO] |  +- org.springframework:spring-core:jar:4.1.4.RELEASE:compile
[INFO] |  +- org.springframework:spring-web:jar:4.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.1.4.RELEASE:compile
[INFO] |  |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:4.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-context:jar:4.1.4.RELEASE:compile
[INFO] |  |     \- org.springframework:spring-expression:jar:4.1.4.RELEASE:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-server:jar:2.14:compile
[INFO] |  |  +- org.glassfish.jersey.core:jersey-common:jar:2.14:compile
[INFO] |  |  |  +- org.glassfish.jersey.bundles.repackaged:jersey-guava:jar:2.14:compile
[INFO] |  |  |  \- org.glassfish.hk2:osgi-resource-locator:jar:1.0.1:compile
[INFO] |  |  +- org.glassfish.jersey.core:jersey-client:jar:2.14:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.2:compile
[INFO] |  |  +- org.glassfish.hk2:hk2-api:jar:2.4.0-b06:compile
[INFO] |  |  |  +- org.glassfish.hk2:hk2-utils:jar:2.4.0-b06:compile
[INFO] |  |  |  \- org.glassfish.hk2.external:aopalliance-repackaged:jar:2.4.0-b06:compile
[INFO] |  |  +- org.glassfish.hk2.external:javax.inject:jar:2.4.0-b06:compile
[INFO] |  |  \- org.glassfish.hk2:hk2-locator:jar:2.4.0-b06:compile
[INFO] |  |     \- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] |  +- org.glassfish.jersey.containers:jersey-container-servlet-core:jar:2.14:compile
[INFO] |  +- org.glassfish.jersey.containers:jersey-container-servlet:jar:2.14:compile
[INFO] |  +- org.glassfish.jersey.ext:jersey-spring3:jar:2.14:compile
[INFO] |  |  +- org.glassfish.hk2:hk2:jar:2.4.0-b06:compile
[INFO] |  |  |  +- org.glassfish.hk2:config-types:jar:2.4.0-b06:compile
[INFO] |  |  |  +- org.glassfish.hk2:core:jar:2.4.0-b06:compile
[INFO] |  |  |  +- org.glassfish.hk2:hk2-config:jar:2.4.0-b06:compile
[INFO] |  |  |  |  +- org.jvnet:tiger-types:jar:1.4:compile
[INFO] |  |  |  |  \- org.glassfish.hk2.external:bean-validator:jar:2.4.0-b06:compile
[INFO] |  |  |  +- org.glassfish.hk2:hk2-runlevel:jar:2.4.0-b06:compile
[INFO] |  |  |  \- org.glassfish.hk2:class-model:jar:2.4.0-b06:compile
[INFO] |  |  |     \- org.glassfish.hk2.external:asm-all-repackaged:jar:2.4.0-b06:compile
[INFO] |  |  \- org.glassfish.hk2:spring-bridge:jar:2.4.0-b06:compile
[INFO] |  \- org.glassfish.jersey.media:jersey-media-json-jackson:jar:2.14:compile
[INFO] |     +- com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:2.3.2:compile
[INFO] |     \- com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:2.3.2:compile
[INFO] |        \- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.3.2:compile
[INFO] +- org.glassfish.jersey.ext:jersey-mvc:jar:2.14:compile
[INFO] |  +- javax.servlet:servlet-api:jar:2.4:compile
[INFO] |  \- javax.ws.rs:javax.ws.rs-api:jar:2.0.1:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.2.1.RELEASE:test
[INFO] |  +- org.mockito:mockito-core:jar:1.10.8:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.1:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  \- org.springframework:spring-test:jar:4.1.4.RELEASE:test
[INFO] +- org.springframework.boot:spring-boot-starter-thymeleaf:jar:1.2.1.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-web:jar:1.2.1.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-webmvc:jar:4.1.4.RELEASE:compile
[INFO] |  +- org.thymeleaf:thymeleaf-spring4:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.thymeleaf:thymeleaf:jar:2.1.4.RELEASE:compile
[INFO] |  |  |  +- ognl:ognl:jar:3.0.8:compile
[INFO] |  |  |  \- org.unbescape:unbescape:jar:1.1.0.RELEASE:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.8:compile
[INFO] |  \- nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:jar:1.2.7:compile
[INFO] \- junit:junit:jar:4.12:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.425 s
[INFO] Finished at: 2015-01-14T23:02:38+09:00
[INFO] Final Memory: 21M/165M
[INFO] ------------------------------------------------------------------------

servlet-apiを検索してヒットした箇所を見るとやはりjersey-mvcが依存していました。

dependency要素にexclusion要素を追加してservlet-apiへの依存を除外した ところIDEからも起動できました。

所感

Spring MVCはMVCと言うだけあってビューを持つアプリケーションはさくさく作れそうな気がしました。

それに対してJAX-RSは単純にJSONを返すというようなAPIを作るのに特化してるなー、と改めて思いました。

まあ、そんな感じで。 おしまい。

]]>
Wed, 14 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2015/01/11/spring_boot.html https://backpaper0.github.io/2015/01/11/spring_boot.html <![CDATA[Spring Bootをやってみた]]> Spring Bootをやってみた

しょぼちむがSpring Bootのハンズオンのレポートを書いていた のでそれを参考に手を動かしてみました。 ブログ通りに写経していったら普通に動いたので楽ちんでした。

ただし私の知識がアレで Twitter Application Management のSettingsタブにある “Allow this application to be used to Sign in with Twitter” にチェックを入れる必要があるっぽいのに気付かなくてそこだけちょっとつまずいた。 Twitterプヨグヤミングしてないのバレる!

HttpServletRequestとHttpSessionをなくす

さて、出来上がったアプリケーションでは HttpServletRequest と HttpSession を使っている箇所があったのでそれらを使わないように変更します。

Note

HttpServletRequest と HttpSession はサーブレットのAPIでありSpringのAPIではありません。 なるべく低レベルのAPIを直接使用しない方がコードがシンプルになるし ユニットテストが書きやすくて嬉しい、というのが個人的な考えです。

まずは HttpServletRequest を消します。 Twitterの認証から戻ってくるときに oauth_verifier というリクエストパラメータを受け取っている所に使われています。 これをTwitterControllerのdoTweetメソッドと同じやり方で受け取るように変更します。

@RequestMapping("accessToken")
public String accessToken(Model model,
        @RequestParam(value = "oauth_verifier", required = true) String verifier)
        throws TwitterException {

次いでHttpSessionを消します。 認証の際に必要となるリクエストトークンとアクセストークンを保持するために HttpSession が使用されているようです。 今回はそれらを格納するセッションスコープのクラスを作ってコントローラーにインジェクションする方法をとります。

まずこれがトークンを格納するセッションスコープのクラス。

package jp.co.bizreach.spring_boot_sample;

import java.io.Serializable;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;

@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS,
        value = WebApplicationContext.SCOPE_SESSION)
public class TwitterAuth implements Serializable {

    private AccessToken accessToken;

    private RequestToken requestToken;

    public AccessToken getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(AccessToken accessToken) {
        this.accessToken = accessToken;
    }

    public RequestToken getRequestToken() {
        return requestToken;
    }

    public void setRequestToken(RequestToken requestToken) {
        this.requestToken = requestToken;
    }
}

TwitterAuthController と TwitterController には次のようにフィールドにインジェクションします。

@Autowired
private TwitterAuth auth;

あとはこのフィールドを使ってトークンを格納したり取り出したりすればオッケーです。

これでサーブレットAPIをなくすことが出来ました!

pom.xmlを作る

しょぼちむのブログより

今回はサンプルアプリを作ってくれていて、基本的にはそれを動かしてみるって感じだったけど、pomファイルの作成のところからやってみたかったかも。

作成しましょう!

サンプルはmvn archetype:generateで空のプロジェクトを作ったあとに pom.xmlを編集してdependencyを追加したように見えます。

適当なディレクトリでmvn archetype:generateを実行します。

mvn archetype:generate

すると色んな雛形が一覧でずらーっと出てくるので使いたいものを番号で指定します。

今回はデフォルトの maven-archetype-quickstart を使用しますので数字は何も入力せず次に進みます。

maven-archetype-quickstart のバージョンを尋ねられます。 既に最新が選択されているのでここも何も入力せず次に進みます。

ここから groupId、artifactId、version、そしてアプリケーションの package を尋ねられます。 適宜入力してそのまま進むと次のようなログが出て空のプロジェクトが作成されます。

[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /Users/backpaper0/src/temp
[INFO] Parameter: package, Value: app
[INFO] Parameter: groupId, Value: sample
[INFO] Parameter: artifactId, Value: spring-boot-sample
[INFO] Parameter: packageName, Value: app
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] project created from Old (1.x) Archetype in dir: /Users/backpaper0/src/temp/spring-boot-sample
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 55.560 s
[INFO] Finished at: 2015-01-12T12:38:58+09:00
[INFO] Final Memory: 14M/95M
[INFO] ------------------------------------------------------------------------

作成されたファイルは次のような感じ。

  • ./pom.xml
  • ./src/main/java/app/App.java
  • ./src/test/java/app/AppTest.java

pom.xmlとHello, world!するだけのクラス(App.java)とassertTrue(true)するだけのテストクラス(AppTest.java)です。

App.java と AppTest.java は要らないので消します。

それからpom.xmlを編集します。

mvn archetype:generateした直後の状態は次のような感じです。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>sample</groupId>
  <artifactId>spring-boot-sample</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>spring-boot-sample</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

これに リファレンスの10.1.1 Maven installation を参考にして parent要素と dependency要素を追加しました。 あとついでにJUnitのバージョンを4.12に上げました。 それとmaven-compiler-pluginの設定を追加してJava 8でビルドされるようにしました。 んでもって、spring-boot-maven-pluginの設定を追加してスタンドアロンJARを作れるようにしました。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>sample</groupId>
  <artifactId>spring-boot-sample</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>spring-boot-sample</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.1.RELEASE</version>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.twitter4j</groupId>
      <artifactId>twitter4j-core</artifactId>
      <version>4.0.2</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

これで概ねハンズオンのpom.xmlに近くなったと思います。

今後の予定

リファレンスには 10.1.2 Gradle installation というのがあったのでGradle化してみたいですね。

それと spring-boot-starter-jersey というのがあるっぽいのでSpring MVCをJAX-RSに置き換えるというのもやってみたいです。

……やらない雰囲気が漂っていますが気にしない方向で!

まとめ

ひとのブログを写経しただけっていう他力本願がひどいブログ初めでした。

今年もよろしくお願い致します。

]]>
Sun, 11 Jan 2015 00:00:00 +0000
https://backpaper0.github.io/2014/12/24/glassfish_create_session_id.html https://backpaper0.github.io/2014/12/24/glassfish_create_session_id.html <![CDATA[GlassFishでセッションIDを生成してるところ]]> GlassFishでセッションIDを生成してるところ

これは GlassFish Advent Calendar 2014 の24日目です。

相変わらずの小ネタです。

以前調べたのですが、 GlassFishでのHttpSession実装クラスは org.apache.catalina.session.StandardSession で、 これはTomcatのコードを利用したものですが、 セッションIDの生成処理は変更されているようです。

なんやかんや辿って行くと com.enterprise.util.uuid.UuidUtilgenerateUuid メソッドに行き着きました。

コードを引用します。

//this method can take in the session object
//and insure better uniqueness guarantees
public static String generateUuid(Object obj) {

    //low order time bits
    long presentTime = System.currentTimeMillis();
    int presentTimeLow = (int) presentTime;
    String presentTimeStringLow = formatHexString(presentTimeLow);

    StringBuilder sb = new StringBuilder(50);
    sb.append(presentTimeStringLow);
    //sb.append(":");
    sb.append(getIdentityHashCode(obj));
    //sb.append(":");
    //sb.append(_inetAddr);
    sb.append(addRandomTo(_inetAddr));
    //sb.append(":");
    sb.append(getNextRandomString());
    return sb.toString();
}

ご覧の通り、

  • システム日付
  • セッションオブジェクトのIdentityハッシュコード
  • ローカルホストのIPアドレス
  • ランダムな文字列

を繋げたものになっています。

ここから呼び出されているメソッドを細かく見て行くと7文字で切ってたりしてマジこれでセキュアなん? とか思ってしまったりしましたが “insure better uniqueness guarantees” とか書かれてるし衝突耐性高くて大丈夫なんでしょうたぶん。

ちなみに org.apache.catalina.session.StandardSessionorg.glassfish.main.web:web-core に、 com.enterprise.util.uuid.UuidUtilorg.glassfish.main.common:common-util に入っています。

というわけであっさりしていますが、以上。

GlassFishさん、来年もお世話になります!

]]>
Wed, 24 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/24/syobotsum.html https://backpaper0.github.io/2014/12/24/syobotsum.html <![CDATA[しょぼつむ #syobochim]]> しょぼつむ #syobochim

これは しょぼちむ Advent Calendar 2014 の24日目です。

というツイートを見て軽い気持ちで作ってみましたが蓋を開けると色んな人が色んな事を書いており、 しょぼちむ本当に良かったね!っていう感じでいっぱいの楽しいアドベントカレンダーもはや24日目となりました。

「しょぼつむ」作りました

さて、今回私はしょぼちむをモチーフにして「しょぼつむ」というゲーム作ってみました。 ゲームのイメージを貼ります。

../../../_images/syobotsum_screen_shot.png

ルールは至ってシンプルです。 画面の下の方に見えているハート・レッドキング・雪だるまを指で弾くと飛んで行って着地します。 制限時間10秒の間に次々と弾いて高く積み上げてください。 雪降るクリスマスの街に積み上がった分だけ女子力が手に入ります。 という、なんかもうこの時点でしょぼちむすまん、と言いたくなる感じのコンセプトです。

積むものによってポイントが異なります。

  • ハート:5ポイント
  • レッドキング:2ポイント
  • 雪だるま:1ポイント

ハートはポイント稼げますが落下速度がのんびりしています。

野良apkファイルを用意しました。 良ければ遊んでみてください。

ソースコードはこちら。

gradlew android:build したらapkをビルドできます。

ちなみに実機で確認していません(キリッ

ゲームを作ってみて

すごく簡単なゲームですがゲーム作り初心者には難しい部分もありました。

しかし技術的な難しさよりもゲームシステムのアイデアが出なかったり絵の準備に手間取ってしまいました。

アドベントカレンダーのネタを考えていたときに「しょぼつむ」という言葉が先に浮かんだので何かを積むものにしようとは思っていたのですが、 ぷよぷよのような落ちものゲームにしようかなー、 それとも将棋崩しのようなものも積むという基本コンセプトからは逸脱しないかなー、 など悩みました。

悩みはしましたがなんせ普段はWebアプリでCRUDの亜種みたいなのばっか作ってる身としては凝った事をやるとしぬと思い、 シンプルなルールになるよう考えた末に前記のようなものになりました。

コーディングも普段やってる事とは異なりましたがなるべく高レベルAPIを利用する事で なんとか形に出来ました。

TODO

とりあえず動きますが未完成です。 次にTODOを記載します。

  • SEとBGMを付けたい
  • ヘルプを組込みたい。ブログに操作方法書くとかアレ
  • フェードアウト時、各パーツの枠のちらつきをなんとかしたい
  • 画像のロードを非同期にしたい
  • 結果画面がおとなしいからもっとわちゃわちゃさせたい
  • ソースコードにコメントほぼないの、たぶん後で困るから覚えてるうちに書いておきたい

特にBGMはせっかくなので作曲したいと考えていましたがそんな時間はなかった(まがお

そもそもパソコンで曲作るのってどうしたら良いですかね? 教えてえろいひと!(他力本願

技術的な話

「しょぼつむ」はlibGDXで開発をしました。

libGDXはJavaで書かれたゲームフレームワークで、ささーっとコードを書くとそのコードをデスクトップアプリ、Androidアプリ、iOSアプリなどにビルド出来るスゴいやつです。

私はAndroidスマホ持っていないしiOS開発者ライセンスも持っていないのでプライベートでスマホアプリ開発はしないのですが、 libGDXであればJava SEで書いてデスクトップで動かせるので私のような者であってもスマホアプリ開発が出来てしまいました。

libGDXの事を詳しく知りたい方は GitHubにあるlibGDXのWiki を読むか「libGDX しんさん」でググると良いでしょう。

まとめ

  • ゲーム作るの難しい
  • libGDXすごく良い

明日のアドベントカレンダーは

しょぼちむの番でフィナーレ ですね!

]]>
Wed, 24 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/15/jersey_optional.html https://backpaper0.github.io/2014/12/15/jersey_optional.html <![CDATA[Jersey 2.14でパラメータの受け取りにOptionalが使えるようになった]]> Jersey 2.14でパラメータの受け取りにOptionalが使えるようになった

Jersey 2.14がリリースされたようです!

で、注目は [JERSEY-2612] です。 この対応のおかげで@QueryParamなどのパラメータをOptionalで定義する事が可能になります。 ただしParamConverterを書く必要はありますが。

ParamConverterってなんやねん!って方は JAX-RSでパラメータの受け取り方をいろいろ試す の後半を読んでくださいませー。

リクエストパラメータをOptionalで受け取るコード例

適当ですがサクッちょとサンプル書きました。

リソースクラスをこちらにも掲載します。 ちょー簡単な例ですが、こんな感じでリソースメソッドの引数にOptionalを使えるようになります。

package example;

import java.util.Optional;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("hello")
public class HelloWorld {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String say(@QueryParam("name") Optional<String> name) {
        return "Hello, " + name.orElse("world") + "!";
    }
}

注意点としては先ほども書きましたがParamConverter実装クラスとParamConverterProvider実装クラスも 自前で準備しなくてはならない事です。 まあ、一度書いたら使い回せるはずなのでサクッと書いておきましょー。

今までこれが出来なかった理由

@QueryParamなどで注釈された引数へ渡される値はSingleValueExtractorのextractメソッドを 通るんですが、Jersey 2.13までのextractメソッドは「値がnullでなければParamConverterなどで変換、 nullなら@DefaultValueで設定された値を返す」という感じの実装になっていました。

その部分を抜粋します。

@Override
public T extract(MultivaluedMap<String, String> parameters) {
    String v = parameters.getFirst(getName());
    if (v != null) {
        try {
            return fromString(v);
        } catch (WebApplicationException ex) {
            throw ex;
        } catch (ProcessingException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ExtractorException(ex);
        }
    } else {
        return defaultValue();
    }
}

このロジックが原因でOptionalのParamConverterを書いてもnullが渡ってくるアレっぷりでした。

しかしこのextractメソッドはJersey 2.14で次のように修正されました。

@Override
public T extract(MultivaluedMap<String, String> parameters) {
    String v = parameters.getFirst(getName());
    try {
        return fromString((v == null && isDefaultValueRegistered()) ? getDefaultValueString() : v);
    } catch (WebApplicationException ex) {
        throw ex;
    } catch (ProcessingException ex) {
        throw ex;
    } catch (IllegalArgumentException ex) {
        return defaultValue();
    } catch (Exception ex) {
        throw new ExtractorException(ex);
    }
}

ご覧の通り「値がnullかつデフォルト値が設定されていればデフォルト値を、 そうでなければ値をParamConverterなどに渡す」という風になっています。

これで値がnullの場合でもParamConverterを通るようになり、Optionalへの変換が可能になりました。

まとめ

  • Jerseyでリクエストパラメータなどの受け取りにOptional使えるようになって嬉しい
  • ハイテンションでブログ書いたら文章やばい

そんな感じでー

]]>
Mon, 15 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/14/kotlin_static_method.html https://backpaper0.github.io/2014/12/14/kotlin_static_method.html <![CDATA[Kotlinでstaticメソッドが定義できるようになったのでJAX-RSリベンジ]]> Kotlinでstaticメソッドが定義できるようになったのでJAX-RSリベンジ

ことりん〜(挨拶)

これは Kotlin Advent Calendar 2014 の14日目です。

夏の終わりに 関西Kotlin勉強会 を開催し、私はKotlinでJAX-RSをやるという発表をしました。 JAX-RSにいくつかあるリクエストパラメータの受け取りかたのうち 「Stringの引数をひとつだけ受け取る”valueOf”という名前のstaticファクトリメソッドを持つクラス」 が実現できませんでした。 そのときのKotlinのバージョン(M7)ではstaticメソッドが定義できなかったからです。

しかしバージョンM9からplatformStaticアノテーションを使用してstaticメソッドを定義できるようになったようです。

というわけでリベンジしました。 次のような感じで書けます。

package app

import kotlin.platform.platformStatic

public class ValueObj private (val value: String) {
  class object {
    platformStatic fun valueOf(value: String) = ValueObj(value)
  }
}

Kotlinの思想がどうあれJava言語、または既存のJavaライブラリとの共存を考慮するとstaticメソッドの 定義は必要だろうなーと思っていたのでこの機能追加は良いと思います。

個人的にはstaticファクトリメソッドを持つバリューオブジェクトを多用するので大変助かります。

おしまい。

今日のコード

]]>
Sun, 14 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/07/tinkerer_hatena_star.html https://backpaper0.github.io/2014/12/07/tinkerer_hatena_star.html <![CDATA[Tinkererにはてなスターを設置した]]> Tinkererにはてなスターを設置した

Tinkerer で書いてるこのブログに はてなスター を設置したのでその辺まとめておきます。 なお、私は modern5 をベースにカスタマイズしたテーマを使用していますので別のテーマだと多少異なるかも知れません。

まずはてなスターのマイページでブログを登録します。 マイページのURLは http://s.hatena.ne.jp/<自分のID> です。

次に _templates/page.html のextraheadブロック( {%- block extrahead -%}{%- endblock -%} に囲まれたところ)に次のJavaScriptコードを書きます。

<script type="text/javascript">
   Hatena.Star.Token = <自分のトークン>;
   Hatena.Star.SiteConfig = {
     entryNodes: {
       'div.main-container': {
         uri: 'window.location',
         title: 'document.title',
         container: 'span.hatenastar'
       }
     }
   };
</script>

<自分のトークン> にはマイページでブログを追加したあとに表示されるトークンを書いてください。

最後に実際にはてなスターを設置する場所となる要素を追加します。 私はエントリのいっちゃん下に置きたかったのでbodyブロックの最後の方に次のspan要素を書きました。

<span class="hatenastar"> </span>

自分のTinkererちからが低すぎて悩んだりもしたけれど、出来てしまえば簡単でした!

より詳しくは http://d.hatena.ne.jp/hatenastar/20070707 を参照くださいですじゃ。

]]>
Sun, 07 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/07/glassfish_asadmin.html https://backpaper0.github.io/2014/12/07/glassfish_asadmin.html <![CDATA[よく使うasadminのコマンドを紹介する]]> よく使うasadminのコマンドを紹介する

これは GlassFish Advent Calendar 2014 の7日目です。

小ネタです。 私がよく使うasadminのコマンドを、引数も書いて実際に使っている感じを出しつつ紹介します。

asadmin list-commands
どんなコマンドあったっけなー、ってときどき確認します。
asadmin list-domain
GlassFishはドメインという単位で管理しますが、このコマンドで今あるドメインを確認します。
asadmin delete-domain domain1
要らんドメインを消します。 ドメインは glassfish/domains/ 以下に作成されますが、ここを心を込めて手動で消しても大丈夫っぽいです。
asadmin create-domain --portbase=9000 domain1
ドメインを作ります。 portbaseを指定すればHTTPは9080、管理は9048という風に良い感じにポートを設定してくれます。
asadmin start-domain domain1
ドメインを起動します。 後述するcreate-jdbc-connection-poolコマンドなど一部のコマンドはドメインが起動していないと実行できなかったりします。
asadmin stop-domain domain1
起動しているドメインを停止します。
asadmin create-jdbc-connection-pool --datasourceclassname org.h2.jdbcx.JdbcDataSource --restype javax.sql.DataSource --property url=jdbc\\:h2\\:sampleDB:user=sa:password=secret samplePool
JDBCコネクションプールを作ります。 propertyにURLを書く場合、コロンをエスケープしないといけないので気をつけましょー。 一番最後の引数(この例でいうとsamplePool)がコネクションプールIDです。 なお、JDBCドライバは glassfish/domains/domain1/lib/ext/ (domain1はドメインの名前なので適宜置き換えてください)に置きます。
asadmin create-jdbc-resource --connectionpoolid samplePool jdbc/sample
JDBCリソースを作ります。 アプリケーションからはここで設定したJNDI名(この例でいうとjdbc/sample)で参照します。 ひとつのJDBCコネクションプールに対して複数のJDBCリソースを作ることもできます。
asadmin create-jvm-options -Xmx1024m
JVMオプションを設定します。 この例では最大メモリサイズを設定しています。 このコマンドで設定したJVMオプションはドメインを再起動したときに反映されます。
asadmin set-log-levels org.seasar.doma=CONFIG
ログレベルを設定します。
asadmin deploy --contextroot=sample --name=sample sample.war
デプロイします。
asadmin redeploy --name=sample sample.war
再デプロイします。 デプロイと再デプロイでコマンドを使い分けなきゃダメなのはちょっと面倒です。
asadmin undeploy sample
アンデプロイします。 引数にはデプロイ時に設定したnameを指定します。

とまあ、よく使うのはこんな所でしょうか。 自分が思っていたより少なかったですね。

簡単ですが、以上になります。 おそまつさまでした。

]]>
Sun, 07 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/07/application_class.html https://backpaper0.github.io/2014/12/07/application_class.html <![CDATA[どうやってApplicationサブクラスの名前取ってきてんの?]]> どうやってApplicationサブクラスの名前取ってきてんの?

これは JavaFX Advent Calendar 2014 の7日目です。

小ネタです。

JavaFXアプリケーションのメインクラスは次のように書きますよね。

import javafx.application.Application;
import javafx.stage.Stage;

public class Hoge extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        //略
    }
}

これを初めて見たとき、launchメソッドにApplicationサブクラスのインスタンスや Classを渡してるわけでもないのにインスタンス化されてstartメソッドが呼ばれるのが キモいなー、と思ったのでした。

で、だいたい予想はつきましたが、Application.javaを読んでみました。 その部分を引用します。

// Figure out the right class to call
StackTraceElement[] cause = Thread.currentThread().getStackTrace();

boolean foundThisMethod = false;
String callingClassName = null;
for (StackTraceElement se : cause) {
    // Skip entries until we get to the entry for this class
    String className = se.getClassName();
    String methodName = se.getMethodName();
    if (foundThisMethod) {
        callingClassName = className;
        break;
    } else if (Application.class.getName().equals(className)
            && "launch".equals(methodName)) {

        foundThisMethod = true;
    }
}

という感じで、スタックトレースを取得して現在のメソッド(launch)のひとつ前の メソッド名(main)とクラス名(Hoge)を取得していました。

予想通り割とキモい方法でクラス名を取ってきていたことが分かって満足です。

JavaFXである必要性の薄い内容でしたが、以上になります。 おそまつさまでした。

]]>
Sun, 07 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/04/validation.html https://backpaper0.github.io/2014/12/04/validation.html <![CDATA[BeanValidationの相関バリデーションとそもそもの話]]> BeanValidationの相関バリデーションとそもそもの話

BeanValidationで相関バリデーションするときに新しいメソッド作って@AssertTrueを使うよ! っていう話があったので、それについて思う事を書きます。

なお、以下はWebアプリケーションでリクエストパラメータをPOJOにマッピングして BeanValidationを行うという流れを想定しています。

@AssertTrueでバリデーション

件の相関バリデーションはたぶんこんな感じです。

public int from;

public int to;

@AssertTrue
public boolean isFromLessThanTo() {
    return from < to;
}

私は、これは

  • メソッド内にバリデーションロジックを書くので再利用性が低い
  • @AssertTrue、そしてbooleanは宣言的ではない

と思っています。

じゃあ、どうすればいいのか

from < to を検証するアノテーションとカスタムバリデータを作りましょう。 そしてそれを付けるメソッドはbooleanではなくタプル(のような何か)を返します。

public int from;

public int to;

@FromLessThanTo
public Pair isFromLessThanTo() {
    return new Pair(from, to);
}

こうする事で、

  • バリデーションのロジックはカスタムバリデータに閉じ込めたので再利用性高まる
  • アノテーションで大小関係があるって分かって宣言的っぽい

となるかと。

そもそも

アノテーションを使ったバリデーションってどうなんでしょうね?

例えば、次のようなコードがあったとします。

@Isbn
public String isbn1;

@Isbn
public String isbn2;

アノテーションを見ればisbn1もisbn2もISBNであるということは分かるのですが、 でもそれってアノテーションの役割じゃなくて型じゃないの?と思うのです。

本来あるべき姿はこんな感じ。

public Isbn isbn1;

public Isbn isbn2;

この場合バリデーションはIsbn型へ変換するときに行うのが良いと思われます。

型よ

基本的にバリデーションは次の順番で行われると思います。

  • 必須バリデーション
  • フォーマットバリデーション(日付とされる値がyyyy/MM/ddになっているか?みたいなことです)
  • 相関バリデーション

必須バリデーションを行うか否かも型で表したい。 Optionalでないものは必須!という感じです。 たぶんそれが良いと思う。

//必須
public Isbn isbn1;

//必須でない
public Optional<Isbn> isbn2;

フォーマットも先述の通り型で表す事ができます。

それから相関バリデーションですが、最初の例であればRangeといった型を作ってそこにfromとtoを 詰め込めばfromとtoの大小関係を型で表す事ができます。

new Range(from, to);

まとめ

まとまりません! もっと理想的なバリデーションフレームワークが欲しい!

というわけでバリデーションへの悩みは尽きません。 悩ましい。

]]>
Thu, 04 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/01/javaee_advent_calendar_2014.html https://backpaper0.github.io/2014/12/01/javaee_advent_calendar_2014.html <![CDATA[JAX-RSを始める #javaee]]> JAX-RSを始める #javaee

これは Java EE Advent Calendar 2014 の1日目です。

今回は参照実装である Jersey を使ってJAX-RSを始めるための環境構築などをだらだら書こうと思います。

ビルドツール

昨今はGradleが人気のような雰囲気漂っていますが、 Maven を使いましょう。

JerseyはMavenのprofileという機能を使っていてGradleではその辺のサポートがないっぽいのでたぶんしんどいです。

……と書きましたが最近のGradleはprofile対応してるようです。

というわけでサンプルにGradleのビルドファイルも追加しました。

Mavenのインストールはバイナリを任意の場所にダウンロードして bin ディレクトリにパスを通せばおkです。

ビルドファイルの準備

Mavenの場合

まず、 mvn archetype:generate してください。 それから出来たpom.xmlを編集します。

次のdependencyManagement要素を追加してください。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.glassfish.jersey</groupId>
      <artifactId>jersey-bom</artifactId>
      <version>2.13</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

それからjersey-bomのpom.xmlを見ながら好きなものを選んでdependency要素に追加します。 とはいえJerseyはサーバ側のAPI、クライアント側のAPI、MVC拡張など多数のJARに別れて提供されているので 何を追加すれば良いのか最初は分からないと思います。

今回はサーバ側のコードを書きたいので、 jersey-server を追加します。 また、Jerseyは GrizzlyJetty などのコンテナ上で動きますが、今回は JDK付属のHttpServer で動かしたいので jersey-container-jdk-http を追加します。 どんなコンテナが使えるかは http://repo1.maven.org/maven2/org/glassfish/jersey/containers/ を参照ください。 最後にJUnitでサクッと走らせてテストするため jersey-test-framework-provider-jdk-http を追加します。

<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-jdk-http</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-jdk-http</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

とまあ、こんな感じで良いでしょう。

でもこんなことチンタラやってられないと思うので https://github.com/backpaper0/sandbox をcloneして jersey-blank ディレクトリ内の pom.xml をご利用ください。 私も大抵、自分が過去に書いたビルドファイルをコピります。

Gradleの場合

こんな感じ?

dependencies {
    compile 'org.glassfish.jersey.core:jersey-server:2.13'
    compile 'org.glassfish.jersey.containers:jersey-container-jdk-http:2.13'
    testCompile 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-jdk-http:2.13'
}

コードを書く

まあ、この辺は適当に、足し算する簡単なやつで。

src/main/java/app/Calc.java を作ります。

package app;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("calc")
public class Calc {

    @Path("add")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return a + b;
    }
}

で、JUnitテストです。 src/test/java/app/CalcTest.java を作ります。

package app;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import javax.ws.rs.core.Application;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

public class CalcTest extends JerseyTest {

    @Test
    public void test() throws Exception {
        int c = target("calc/add").queryParam("a", 2)
                                  .queryParam("b", 3)
                                  .request()
                                  .get(int.class);
        assertThat(c, is(5));
    }

    @Override
    protected Application configure() {
        return new ResourceConfig(Calc.class);
    }
}

test-frameworkを使うととても簡単にJUnitテストを書ける事が分かると思います。

テスト走らせる

IDEから実行するかMavenで。

mvn test

簡単ですね!

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running app.CalcTest
11 30, 2014 10:55:12 午後 org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory$JdkHttpServerTestContainer <init>
情報: Creating JdkHttpServerTestContainer configured at the base URI http://localhost:9998/
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.109 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

mainメソッドでサーバーを立てる

JUnitテストを走らせている事からもお分かり頂けると思いますが、 簡単にサーバーを立てる事もできます。

こんな感じ。

package app;

import java.io.IOException;
import java.net.URI;

import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import com.sun.net.httpserver.HttpServer;

public class Server {

    public static void main(String[] args) throws IOException {
        URI uri = URI.create("http://localhost:8080/rest/");

        ResourceConfig rc = new ResourceConfig();
        rc.register(Calc.class);

        HttpServer httpServer = JdkHttpServerFactory.createHttpServer(uri, rc);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> httpServer.stop(0)));

        System.out.println("JAX-RS started");
    }
}

アプリケーションサーバにデプロイする

GlassFishにデプロイする場合はdependencyのscopeをprovidedにしてWARファイルを作ってそれをデプロイすれば良いと思います。

Tomcatにデプロイする場合は jersey-container-jdk-http を消して、 jersey-container-servlet を追加してWARファイルを作りましょう。 こんな感じです。

<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>

私はServlet APIに依存しないよう作る方がポータビリティが高そうで好きなのでscopeをruntimeにしています。 Servlet APIじゃんじゃん使いたい場合はscopeをcompileにしてください。

まとめ

というわけでJerseyを使用したJAX-RSの導入部分、如何でしたでしょうか? 簡単ですよね? 特にJava EEの一部なのにアプリケーションサーバがなくても簡単に使えるのが良いですよね!ね!

最後に、手前味噌ですがJAX-RSの参考資料を挙げておきます。

はー、これらの資料もJAX-RS 2.0にアップデートしないといけないなー(しろめ

簡単ですが、以上。

]]>
Mon, 01 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/01/syobochim_advent_calendar_2014.html https://backpaper0.github.io/2014/12/01/syobochim_advent_calendar_2014.html <![CDATA[Tinkererのしょぼちむテーマ作った #syobochim]]> Tinkererのしょぼちむテーマ作った #syobochim

これは しょぼちむ Advent Calendar 2014 の1日目です。

しょぼちむ とは、

そんな若手エンジニアです。

このアドベントカレンダーではみんなで毎日しょぼちむに関する何かを書こうというもので、 作った当初は全日程埋まるか本人も心配していましたが割と早々に埋まったので良かったね、っていう。

Java EE アドベントカレンダーよりも早く埋まったのは少々複雑でありますが! いや、でも、埋まって良かったね!

本題

さて、今回はこのブログの基盤となっているブログツール、 Tinkerer のカスタムテーマを作ってみました。 と言ってもゼロから作るようなセンスは皆無なので、組込みの modern5 というテーマをコピーして ウルトラ怪獣としょぼちむカラーを混ぜ込んでみました。

今後、もとのテーマに戻した場合を考慮してスクリーンショット貼っておきます。

../../../_images/syobochim-theme.png

ヘッダ、本文、サイドバーなどの構成や基本的なカラーリングは modern5 のままですが、 ヘッダの背景色とリンクの色を変えてしょぼちむカラーにし、ヘッダの背景画像とセクションの見出しに ウルトラ怪獣の絵をはめ込みました。 余談ですが、私はバルタン星人が好きです(V)o\o(V)フォフォフォ。

あと、ブログタイトルをしょぼちむのブログっぽくするためbefore疑似要素、after疑似要素で文章を足しています。 念のため書いておきますが、このブログのタイトルは「裏紙」です。

……といった所で、残念ながらだいぶ力尽きました。 己のHTMLちから不足、CSSちから不足、Tinkererちから不足などに凹みました。

まあやり過ぎるよりは良いかー、と言う感じでどうかひとつ! このとおりだ!

まとめ

ここまで”しょぼちむカラー”の説明なし!

おまけ

セクションの見出しの前に出してるレッドキングがちょっとかわいく描けた気がするので もうちょい大きいサイズも置いておきます。

../../../_images/redking.png
]]>
Mon, 01 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/12/01/irof_advent_calendar_2014.html https://backpaper0.github.io/2014/12/01/irof_advent_calendar_2014.html <![CDATA[スタックトレースろふ #irof]]> スタックトレースろふ #irof

これは いろふ Advent Calendar 2014 の1日目です。

いろふアドベントカレンダーとは、我らが いろふさん への愛をワールドワイドなインターネッツに大公開する感じのやつです。

いろふさんをご存じない方はTwitterでヒョローをキメて、著書である養成読本を熟読すると良いですよ!

本編という名の出オチ

こちらのリポジトリをcloneして、

irofディレクトリに移動し gradlew run してください。

するとこうなります。

../../../_images/stacktracerof.png

なんといろふさんが例外吐いて終了しました! (-∧-;) ナムー

投げっぱなしの解説

Java言語ではメソッド名に空白や記号を使えませんがクラスファイルとJVM的にはもうちょい制約緩いっぽいので その辺を利用してスタックトレースでいろふさんを描きました。

いろふさんのアイコンからAAを作ったのは次のサービスです。

で、クラスファイルを書き出したのはASMというライブラリです。

ASMの ClassWriterGeneratorAdapter を使えば割と簡単にクラスファイルを書き出す事ができます。 詳しくはGeneratorAdapterのJavadocと今回のいろふソースコードを参照ください。

それと今回は使っていませんが ClassReader を使うとクラスファイルを読む事ができます。

例えばインスタンスメソッドを実行してるコードを見つけて標準出力に書き出すようなアレは次のように書けます。

ClassReader reader = new ClassReader("irof.Irof");

MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM5) {

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        if (opcode == Opcodes.INVOKEVIRTUAL) {
            System.out.printf("owner=%1$s name=%2$s desc=%3$s%n", owner, name, desc);
        }
    }
};

ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5) {

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return methodVisitor;
    }
};

reader.accept(classVisitor, 0);

ちなみにASMはJDKにも入っています。 rt.jarcom.sun.xml.internal.ws.org.objectweb.asm 以下がそうですね。 JAX-WSに使われているようです。

あと jersey-server にも使われています。 jersey.repackaged.org.objectweb.asm 以下がそうです。

まとめ

いざやってみるとすんなり出来ちゃって変態度が低すぎてアレですが、まあいいか。

]]>
Mon, 01 Dec 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/12/java_engineer_training_book.html https://backpaper0.github.io/2014/11/12/java_engineer_training_book.html <![CDATA[「Javaエンジニア養成読本」読んだ]]> 「Javaエンジニア養成読本」読んだ

献本して頂きました。 私を推薦してくれたのざきひろふみさん、ありがとうございます!

この本は経験の浅いJavaエンジニアがふつうのJavaエンジニアになるために必要な技術的な知識 (と技術以外の知識) を一度に取り入れる事ができるような内容になっています。

「特集1」ではJava入門という事で誰もが通る道について解説されています。 ただしよくある入門書のように制御文やintの取りうる範囲、演算子の優先順位などをちまちま解説したりはしません。

その代わりJava言語の要であるクラスについて2章をまるごと使って解説しています。 トップレベルのクラス、インナークラス、staticなネストしたクラス、匿名クラスの違いを説明できますか? この本を読めば説明できるようになります。

また、例外の扱いかたについても実践的な視点で書かれています。 しっかり読んで e.printStackTrace() から卒業しましょう。

「特集2」ではラムダ式とStream APIについてコンパクトながらも分かりやすく解説しています。 文法や使い方だけでなくラムダ式がなぜ必要になったのかもよく分かります。

「特集3」はJava EEです。 広大なJava EEの仕様群からServlet、JSF、JAX-RS、JPA、CDI、EJBをピックアップして解説しています。 Java EEの主要技術の雰囲気を掴めるのではないでしょうか。

「特集4」ではJavaの技術ではなくチーム開発に必要な知識として Git、Maven、JUnit、Jenkinsについてそれらが必要となる背景や導入方法、 使用上の注意点などが解説されています。 もはやEclipseでWAR作ってるなどと言う現場はないと思いますが、 これら(あるいはこれらに類する他のツール)を導入していない場合はこの特集が役立つでしょう。

ページは前後しますが、「巻頭記事」ではJavaの歴史とJavaにまつわる(しょーもない)ネタを解説しています。 これを一読しておくとTwitterをより楽しめるようになりますたぶん。

そして一番後ろに記載されている「一般記事」は少々趣が変わり、 受託開発の現場で必要となるスキルが簡潔にまとめられています。 Excelにスクリーンショットを貼付けるといった (´;ω;`)ウッ… となるスキルにも触れていますが、 Java以外にもこういうスキルが必要なんだなと理解できる内容になっています。

細かい感想・気になった点など

というわけで細かい部分を挙げていきます。

  • 29ページ。

    通常の業務システムを開発する中でアノテーションを作成することは少ないので”

    CDIというものがあってだな(ry

  • 30ページ。

    インナークラスに名前付き定数を持てるの知らなかった。 勉強になりました!

  • 34ページ、リスト19。

    複数コンストラクタを用意する場合、 オプション引数は後ろの引数(ここでいうと b )にする方が一般的と思います。

  • 35ページ。

    例外オブジェクトには例外が発生した時の状態が詰め込まれてます。

    「状態」よりも「情報」の方がしっくりくるかなー。 前章で可変と不変について言及しているのも相まって分かりにくい気がしました。

  • 38、39ページ。

    InvocationTargetException は他のリフレクション例外と意味合いがちょっと違うと思っています。 他のリフレクション例外が「指定されたメソッドを実行できなかった」のに対して InvocationTargetExceptionは「指定されたメソッドを実行中に例外が発生した」という状況です。 それによってハンドリングが変わるかどうかはケースバイケースとは思いますが。 あとここはマルチキャッチについて書いてて、たまたまリフレクション使っただけなので InvocationTargetExceptionの扱いとか関係無いか。 まあ気になったということで。

    気になったと言えば例外の中では InterruptedException の扱いにも注意が必要と思うので 言及しておいたほうが良いのではと一瞬思いましたが、 後のほうでマルチスレッドはうかつに触るなって書いてあるしまあいいか。

  • 43ページ。

    Java 1.7のtry-with-resources構文のために追加された Throwable#addSuppressed(Throwable) も使えそうですね。

  • 68ページ、リスト6。69ページ。

    Files.lines() をtry-with-resourcesでクローズしてるのは流石だと思いました。 ここはハマりどころになりそうな気がしますねー。

  • 87ページ、リスト5。

    ボタンの押下時にAjaxで処理

    “押下”の現場感すごい。

  • 95、96ページ。

    JAXB経由のJSONプロバイダはJAX-RSの標準仕様ではない(ですよね?)から注意が必要ですね。

  • 98ページ、リスト3

    JPAにおけるReadとUpdate。 mergeはupdateではなくて永続コンテキスト外のエンティティを 永続コンテキストに突っ込む行為と思います。 em.find(clazz, id) したエンティティは永続コンテキストで管理されているので mergeする必要はないですよね? ……とか思っていたら100ページでmergeの説明があった。

  • 105ページ。

    モックによってテスト容易性を高めたり、

    具体例が欲しい気がしました。 CDIのalternativeはbeans.xmlを編集しなきゃいけなかったりして面倒い。 個人的にはCDIを使う場合はユニットテストにはGuiceを使うのが楽で良いと思っています。

あと、JVMへの言及が見当たらないのが気になりました。 -Xmx とか。

Java EEは、範囲が広くて仕様がでかいので仕方ないのですが、 内容が薄めだと感じてしまいました。 いっそJAX-RSやCDIなどの各仕様の説明はもっとシンプルにして、 チュートリアルを手厚くするという手もあったのかもかなー、などと無責任に思いました。

特にServletとEJBの説明はなくても良かったかも知れませんね。 すっぴんのサーブレットは使わないしEJBはトランザクション境界が欲しいってのが殆どと思いますし。 デカい案件やったことないので見当違いの意見かもしれませんが。

まとめ

というわけで、色々と細かいことも書きましたが総合的には間違い無く良書だと思っています。 冒頭にも書きましたがこの本は経験の浅いJavaエンジニアの皆様へオススメです。 この本を読んで学んで楽しいJavaライフを送りましょう!

あなたと!

(続きははてなブックマークのコメントでお願いします)

執筆陣のブログエントリ

まだ買ってないひとはキクタローさんのブログのアフィリンクから買おう!

]]>
Wed, 12 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/08/semicolonless_tail_call_optimization.html https://backpaper0.github.io/2014/11/08/semicolonless_tail_call_optimization.html <![CDATA[セミコロンレスJavaで末尾再帰の最適化]]> セミコロンレスJavaで末尾再帰の最適化

前回 はセミコロンレスJavaで再帰ができる事が分かりました。

ただし再帰しすぎるとスタックオーバーフローでしにます。

再帰による 1 + 2 + ... + n を見てみましょう。

public class SemicolonlessRecursion {

    public static void main(String[] args) {
        if (java.util.stream.Stream
            .of(Integer.parseInt(args[0]))
            .flatMap(n -> java.util.stream.Stream
            .<F<Integer, Integer>> of((f, m) -> m < 1 ? 0 : m + f.apply(f, m - 1))
            .map(sum -> sum.apply(sum, n)))
            .peek(System.out::println)
            .count() > 0) {
        }
    }

    interface F<P, R> extends java.util.function.BiFunction<F<P, R>, P, R> {}
}

とっても分かりやすいコードですが n10000 程度を与えただけでスタックオーバーフローになります。

この再帰を末尾再帰にして最適化を行うのが今回の目的です。

普通のJavaで末尾再帰最適化

最初からセミコロンレスJavaで考えてもしんどいだけなので、 まずは普通のJavaで末尾再帰最適化版のコードを書いてみます。 実装するに当たって 「Javaによる関数型プログラミング」 の7章を参考にしました。

import java.util.Optional;
import java.util.stream.Stream;

public class TailCallOptimization {

    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);

        F sum = (f, p, r) -> p < 1 ? done(r) : call(() -> f.apply(f, p - 1, r + p));

        TailCall t = sum.apply(sum, n, 0);

        Integer result = Stream.iterate(t, TailCall::get)
                               .map(TailCall::result)
                               .filter(Optional::isPresent)
                               .map(Optional::get)
                               .findFirst()
                               .get();

        System.out.println(result);
    }

    interface F {

        TailCall apply(F f, Integer p, Integer r);
    }

    static TailCall call(TailCall t) {
        return t;
    }

    static TailCall done(Integer result) {
        return new TailCall() {

            @Override
            public TailCall get() {
                throw new UnsupportedOperationException();
            }

            @Override
            public Optional<Integer> result() {
                return Optional.of(result);
            }
        };
    }

    interface TailCall {

        TailCall get();

        default Optional<Integer> result() {
            return Optional.empty();
        }
    }
}

多少セミコロンレスJavaへの変換を意識していますが普通のJavaです。 これをセミコロンレスJavaにしていきます。

セミコロンレス化の布石

Java 8時代におけるセミコロンレスJavaの鍵はラムダ式だと思っています。 値を返すメソッドの定義が出来ないセミコロンレスJavaですが、 ラムダ式を使う事でセミコロンレスに関数を定義する事が可能です。

//ふたつのintを足して返す関数を定義して2, 3に適用する
if (java.util.stream.Stream
    .<java.util.function.BinaryOperator<Integer>> of((a, b) -> a + b)
    .map(add -> add.apply(2, 3))
    .peek(System.out::println)
    .count() > 0) {
}

ラムダ式を使う為に必要となるのは関数型インターフェースです。 セミコロンレスJavaではインターフェースの定義は出来ますが、その中でメソッド定義が出来ません。 ただし、幸いにもJavaの標準APIには関数型インターフェースが豊富に用意されているので それらをextendsすることで用途に特化した関数型インターフェースを手に入れる事ができます。

まず TailCall を関数型インターフェースにする事から始めましょう。 ここでの課題は get()result() の一本化です。 今のままではどうしても匿名クラスを導入する必要があります。

TailCallOptional<Integer>Pair を返す Supplier とすることで TailCall を関数型インターフェースにできました。

interface TailCall extends Supplier<Pair<TailCall, Optional<Integer>>>{}

これにより done(Integer) が返す値を匿名クラスではなくラムダ式で書けるようになりました。

static TailCall done(Integer result) {
    return () -> new Pair<>(null, Optional.of(result));
}

また call(TailCall) は次のように変更します。

static TailCall call(Supplier<TailCall> t) {
    return () -> new Pair<>(t.get(), Optional.empty());
}

こうすることで関数 sum は次のように書けます。

F sum = (f, p, r) -> p < 1 ? done(r) : call(() -> f.apply(f, p - 1, r + p));

それから結果を求める Stream 操作ですが、 普通の再帰版では TailCallget() を呼び出すことで Stream を構築していましたが get()Pair<TailCall, Optional<Integer>> を返すようにしたので、 Pair<TailCall, Optional<Integer>>Stream を構築するようにします。

Stream.iterate(new Pair<>(t, Optional.<Integer> empty()),
               p -> p.getKey().get())
      .map(Pair::getValue)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst()
      .get();

ここまでのコード全体を次に記載します。

import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

import javafx.util.Pair;

public class TailCallOptimization {

    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);

        F sum = (f, p, r) -> p < 1 ? done(r) : call(() -> f.apply(f, p - 1, r + p));

        TailCall t = sum.apply(sum, n, 0);

        Integer result = Stream.iterate(new Pair<>(t, Optional.<Integer> empty()),
                                        p -> p.getKey().get()).map(Pair::getValue)
                               .filter(Optional::isPresent)
                               .map(Optional::get)
                               .findFirst()
                               .get();

        System.out.println(result);
    }

    interface F {

        TailCall apply(F f, Integer p, Integer r);
    }

    static TailCall call(Supplier<TailCall> t) {
        return () -> new Pair<>(t.get(), Optional.empty());
    }

    static TailCall done(Integer result) {
        return () -> new Pair<>(null, Optional.of(result));
    }

    interface TailCall extends Supplier<Pair<TailCall, Optional<Integer>>> {}
}

そしてセミコロンレスへ……

あとはちょっとずつまとめたりなんやかんやしてセミコロンレスJavaに変更していきます。

というわけでセミコロンレスJavaで末尾再帰最適化を行ったコードが次になります。

public class SemicolonlessTailCallOptimization {

    public static void main(String[] args) {
        if (java.util.stream.Stream
            .of(Integer.parseInt(args[0]))
            .flatMap(n -> java.util.stream.Stream
            .<F> of((f, pr) -> pr[0] < 1
                ? () -> new javafx.util.Pair<>(null, java.util.Optional.of(pr[1]))
                : () -> new javafx.util.Pair<>(f.apply(f, new int[] { pr[0] - 1, pr[1] + pr[0] }), java.util.Optional.empty()))
            .<TailCall> map(sum -> sum.apply(sum, new int[] { n, 0 })))
            .map(t -> java.util.stream.Stream
            .iterate(new javafx.util.Pair<>(t, java.util.Optional.<Integer> empty()), p -> p.getKey().get())
            .map(javafx.util.Pair::getValue)
            .filter(java.util.Optional::isPresent)
            .map(java.util.Optional::get)
            .findFirst()
            .get())
            .peek(System.out::println)
            .count() > 0) {
        }
    }

    interface F extends java.util.function.BiFunction<F, int[], TailCall> {}

    interface TailCall extends java.util.function.Supplier<javafx.util.Pair<TailCall, java.util.Optional<Integer>>> {}
}

まとめ

セミコロンレスJavaでも末尾再帰の最適化が出来る事が分かりました。 これによりセミコロンレスJavaがまた一歩、実用的な言語へと近づいたと思われます。

なお、今回は javax.util.Pair を使用しましたが、これが大変便利でした。 特にふたつの値を返す場合に今までは配列あたりを使用していたのでキャストが必須になっていましたが、 Pair があればキャストも不要でコードがすっきりしました。 また、ふたつ以上の値を返す場合は Pair<T, Pair<U, V>> などとすれば良いですね。

というわけでこれからもセミコロンレスJavaの可能性を探って行きたいと思います。

]]>
Sat, 08 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/03/semicolonless_java_fibonacci_without_z_combinator.html https://backpaper0.github.io/2014/11/03/semicolonless_java_fibonacci_without_z_combinator.html <![CDATA[セミコロンレスJavaでフィボナッチをZコンビネータなしで]]> セミコロンレスJavaでフィボナッチをZコンビネータなしで

セミコロンレスJavaでフィボナッチ でZコンビネータ使って再帰だ!(ドヤ) とか書きましたがそんなものは必要なかった。

public class SemicolonlessFibonacci {

    public static void main(String[] args) {
        if (java.util.stream.Stream.<F> of((f, n) -> n <= 1 ? n : f.apply(f, n - 2) + f.apply(f, n - 1))
            .map(f -> f.apply(f, Integer.parseInt(args[0])))
            .peek(System.out::println).count() > 0) {
        }
    }

    interface F extends java.util.function.BiFunction<F, Integer, Integer> {
    }
}

難しく考え過ぎんなって話ですね_(:3」∠)_

]]>
Mon, 03 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/02/semicolonless_java_fibonacci.html https://backpaper0.github.io/2014/11/02/semicolonless_java_fibonacci.html <![CDATA[セミコロンレスJavaでフィボナッチ]]> セミコロンレスJavaでフィボナッチ

概要:ネタでしかない

ある項のフィボナッチ数を書き出す

結論から行きましょう。

ある項のフィボナッチ数を書き出すプログラムですが、こんなコードになりました。 うわ横に長え。

public class SemicolonlessFibonacci {

    public static void main(String[] args) {
        if (java.util.stream.Stream.<java.util.function.Function<F, F>> of(f -> n -> n <= 1 ? n : f.apply(n - 2) + f.apply(n - 1))
            .map(f -> ((java.util.function.Function<G, F>) (x -> f.apply(y -> x.apply(x).apply(y)))).apply(x -> f.apply(y -> x.apply(x).apply(y))))
            .map(fib -> fib.apply(Integer.parseInt(args[0])))
            .peek(System.out::println).findFirst().orElse(0) == 0) {
        }
    }

    interface F extends java.util.function.Function<Integer, Integer> {}

    interface G extends java.util.function.Function<G, F> {}
}

これを SemicolonlessFibonacci.java という名前で保存して javac SemicolonlessFibonacci.java でコンパイルし、 java -cp . SemicolonlessFibonacci 10 というふうに実行してください。

引数をいろいろ変えて試すと標準出力にフィボナッチ数が書き出されることがお分かり頂けると思います。

やりたかったこと

今回、実現したかったことはステートレスな再帰です。

セミコロンレスJavaでは戻り値をもつメソッドを定義できない( return にはセミコロンが必須のため ) という制限のためミュータブルなオブジェクトを引数にしてそれを更新することでメソッドの呼び出し元に値を戻すしかありません。 たぶん。

//戻り値のあるメソッドはreturnでセミコロン必要
public int get() {
    return 0;
}

//ミュータブルな引数で値を戻す
public void set(List<String> holder) {
    if (holder.add("hoge")) {}
}

セミコロンレスJava 8からはラムダ式を使えば戻り値をもつ処理を定義できるようになりました。

if (Stream.<IntBinaryOperator> of((n, m) -> n + m) //二つのintを足して返す関数を定義
          .map(f -> f.applyAsInt(2, 3))            //関数適用
          .peek(System.out::println)               //出力
          .findFirst().isPresent()) {
}

ですが、自身を再帰呼び出しすることはできません。

//これはセミコロンJava……ていうかJava
IntUnaryOperator sum = n -> n == 0 ? 0 : n + sum(n - 1);
                                           //~~~ コンパイルエラー

Zコンビネータ

これらの問題を解決するのがZコンビネータです。

この辺は全然詳しくないんですがラムダ計算で再帰を行うことが出来るアレっぽいです。 きしださんのエントリも参考にさせて頂きました。

再帰するにはYコンビネータというのもあるようですが正格評価戦略の言語ではスタックオーバーフローになるっぽいです。 ていうかなりました。

というわけでZコンビネータです。 その定義は次の通りです。

Z = λf. (λx. f (λy. x x y)) (λx. f (λy. x x y))

これをJavaで書くとこんな感じになりました。

static F z(Function<F, F> f) {
    Function<G, F> a = x -> f.apply(y -> x.apply(x).apply(y));
    G b = x -> f.apply(y -> x.apply(x).apply(y));
    return a.apply(b);
}

interface F extends Function<Integer, Integer> {}

interface G extends Function<G, F> {}

このzメソッドを用いてフィボナッチ数を求める処理を再帰で書いたのが次になります。

Function<F, F> g = f -> n -> n <= 1 ? n : f.apply(n - 2) + f.apply(n - 1);
                                        //~~~~~~~~~~~~~~   ~~~~~~~~~~~~~~ この辺が再帰

F fib = z(g);

System.out.println(fib.apply(11)); //11番目のフィボナッチ数を出力する

あとはセミコロンを消す為になんやかんやいろいろやって一番最初のコードになりました。

まとめ

Java 8時代になりセミコロンレスJavaでも再帰を使えるようになりました。 これによりセミコロンレスJava 8が秘めたる可能性を更に感じる事ができました。

今回は末尾再帰最適化まで考える力は残っていませんでしたが、 「Javaによる関数型プログラミング」 の7章を参考にすればなんとかなるかもしれません。 ならないかもしれません。

みなさんもセミコロンレスJavaをはじめてみませんか? みませんね。 はい、ごめんなさい。

続き: セミコロンレスJavaでフィボナッチをZコンビネータなしで

]]>
Sun, 02 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/01/prefix_domain.html https://backpaper0.github.io/2014/11/01/prefix_domain.html <![CDATA[前方一致の検索条件とドメインクラス #doma2]]> 前方一致の検索条件とドメインクラス #doma2

Doma 2 で前方一致の検索条件をドメインクラスでどう扱うか考えた話です。

馬鹿正直にドメインクラスを使う

SIerで業務アプリ作ってると検索して結果をグリッド表示する画面とかよく作ると思うんですが、 その際に前方一致の検索条件を扱う事も多いのです。

例えば従業員を検索するときに条件に従業員番号と所属部門番号を使えるとします。 加えて、どちらも前方一致で検索するとします。 この検索を行うDAOメソッドを何も考えずに書くとこうなります。

@Select
List<Emp> select(EmpId empId, DeptId deptId);

ちなみにSQLファイルは雰囲気こんな感じで。

SELECT /*%expand */*
  FROM emp
 WHERE empId LIKE /* empId */'AA%'
   AND deptId LIKE /* deptId */'BB%'

EmpIdは従業員番号を表すドメインクラスです。 検索条件は前方一致、つまり従業員番号の後ろが欠けた中途半端な状態のものが渡される可能性があります。 そういった項目にEmpIdを使用するのはおかしいのではないでしょうか?

Stringを使う

そこで検索条件を汎用的な型であるStringに変更します。

@Select
List<Emp> select(String empId, String deptId);

EmpIdの不適切な使用はなくなりましたが、 select(deptId, empId) というふうに引数の順番を間違えてもコンパイル時に検出されなくなってしまいました。

ドメインクラスPrefix<DOMAIN>を作る

というわけで考えたのがPrefix<DOMAIN>というドメインクラスです。

次にコードを記載します。

import java.util.Optional;

import org.seasar.doma.Domain;

@Domain(valueType = String.class, factoryMethod = "of")
public class Prefix<DOMAIN> {

    private final String value;

    private Prefix(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public static <T> Prefix<T> of(String value) {
        return Optional.ofNullable(value).map(x -> new Prefix<T>(x)).orElse(null);
    }
}

型パラメータDOMAINには他のドメインクラスをバインドします。

このPrefix<DOMAIN>を使用するとDAOメソッドは次のようになります。

@Select
List<Emp> select(Prefix<EmpId> empId, Prefix<DeptId> deptId);

これで、

  • ドメインクラスに適しない前方一致に使用するような中途半端な状態を持てる
  • DAOメソッドの引数の順を間違えてもコンパイル時に検出できる

という思いを実現する事ができました。 現時点ではベターな手法だと思っています。

これはドメインクラスをジェネリックなクラスに出来るが故にとれた手法ですが、 そういった事ができるようになったのは実は私の要望だったりします。

あのときはこんなことに使えるとは露程も思っていませんでした(・ω<) テヘペロ

Doma 2良いよ!

]]>
Sat, 01 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/11/01/funcitonal_programming_in_java.html https://backpaper0.github.io/2014/11/01/funcitonal_programming_in_java.html <![CDATA[「Javaによる関数型プログラミング」読んだ]]> 「Javaによる関数型プログラミング」読んだ

さくらばさんに献本して頂きました。 ありがとうございます!

物理的に薄い本ですし、ラムダ式と関数型プログラミングへの入門として良い本だと思います。 チームの後輩に読んで欲しい。

関数型プログラミングへの入門に丁度いいということで、数年前にコップ本で入門を済ませていた私としては少々物足りない気がしました。 ただし7章は末尾再帰の最適化を行うという内容で、そこはJavaコンパイラはサポートしていない部分なので興味深く読みました。 恥ずかしながら、末尾再帰の最適化を自分で書くという発想は無かったので参考になります。

以下、気になった点を挙げます。 タイポも含む。

  • 2〜3ページ。宣言的なコードとはどういうことかを、 いきなりラムダ式を登場させるのではなくJava 7までの語彙で説明しているのが良いですね。

  • 28ページの例2-7。 これメソッド参照になっていないのでコンパイルエラーですね。

  • 70ページ。

    JDKの新しい ClosableStream インターフェース

    とありますが、そのようなクラスはありません。

    リリース前にはあったようですが be6ca7197e0e あたりで削除されました。 ちなみに ClosableStream じゃなくて CloseableStream でした。

  • 89ページの例4-17。 コードを引用します。

    try {
      final URL url =
        new URL("http://ichart.finance.yahoo.com/table.csv?s=" + ticker);
    
      final BufferedReader reader =
        new BufferedReader(new InputStreamReader(url.openStream()));
      final String data = reader.lines().skip(1).findFirst().get();
      final String[] dataItems = data.split(",");
      return new BigDecimal(dataItems[dataItems.length - 1]);
    } catch(Exception ex) {
      throw new RuntimeException(ex);
    }
    

    これ、readerがcloseされていません。 readerをtry-with-resourcesで囲むべきと思います。

    try {
      final URL url =
        new URL("http://ichart.finance.yahoo.com/table.csv?s=" + ticker);
    
      try(final BufferedReader reader =
          new BufferedReader(new InputStreamReader(url.openStream()))) {
        final String data = reader.lines().skip(1).findFirst().get();
        final String[] dataItems = data.split(",");
        return new BigDecimal(dataItems[dataItems.length - 1]);
      }
    } catch(Exception ex) {
      throw new RuntimeException(ex);
    }
    
  • 130〜135ページのインスタンス化の遅延。 面白いアプローチですが最終的に出来上がったコードは少々分かりにくかったし、synchronizedを使っていたのでConcurrency Utilitiesでもうちょっと良い感じに書けるんじゃ? と思い色々考えた挙げ句、次のようなコードを書いてみましたがたいして分かりやすくなりませんでした_(:3」∠)_

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    import java.util.concurrent.atomic.AtomicReference;
    
    public class HoderNaive {
    
        private final AtomicReference<FutureTask<Heavy>> heavy = new AtomicReference<>();
    
        public Heavy getHeavy() {
            if (heavy.compareAndSet(null, new FutureTask<>(Heavy::new))) {
                heavy.get().run();
            }
            try {
                return heavy.get().get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e.getCause());
            }
        }
    }
    
  • 161ページ。

    正しい5の階乗と、120の階乗の一部

    120ではなく20000の階乗ですね。

まとめ

関数型プログラミングにまったく触れたことがない人や若手にJava 8を教える立場にある人にはおすすめです。

コップ本を読んだ程度には関数型プログラミングに触れたことがある人は7章だけ読みましょう。

]]>
Sat, 01 Nov 2014 00:00:00 +0000
https://backpaper0.github.io/2014/10/26/uragamiattack.html https://backpaper0.github.io/2014/10/26/uragamiattack.html <![CDATA[迎撃されてきた #uragamiattack]]> 迎撃されてきた #uragamiattack

土曜日は迎撃会、日曜日はプチ観光で東京勢にめちゃくちゃ甘えさせて貰って幸せいっぱいほくほくの週末でした。

この週末で私に関わってくれたみなさん、本当にありがとうございました!

特にゆとりさんは初日に幹事をしてくれた上に翌日も遊んでくれてありがとうございました!

それとしょぼちむも二日連続で愛人やってくれてありがとうございました!

こんな私ですが、今後とも宜しくお願いしまっす╭( ・ㅂ・)و ̑̑ グッ !

]]>
Sun, 26 Oct 2014 00:00:00 +0000
https://backpaper0.github.io/2014/10/26/java_8_hotspot_meeting.html https://backpaper0.github.io/2014/10/26/java_8_hotspot_meeting.html <![CDATA[Java 8 HotSpot meetingを開催した #kanjava]]> Java 8 HotSpot meetingを開催した #kanjava

上には上がいるもんだなぁ、と白目剥きながらセッションを拝見しました。

以上。

]]>
Sun, 26 Oct 2014 00:00:00 +0000
https://backpaper0.github.io/2014/10/04/stream_collect.html https://backpaper0.github.io/2014/10/04/stream_collect.html <![CDATA[Streamのcollectメソッドを学ぶ]]> Streamのcollectメソッドを学ぶ

Stream にある数多くのメソッドの中でも分かり辛い感じがする collectメソッド について学びます。

collect メソッドの概要

端的に述べると collectメソッドStream<T>R に変換する操作です。

より詳しく述べると、 Stream の各要素( T )を中間コンテナ( A )に折り畳んだ後に最終的な結果( R )に変換する操作です。

括弧内のアルファベットは Collector が持つ3つの型変数に対応しています。

  • T : Streamの要素の型
  • A : ミュータブル な中間コンテナの型
  • R : 最終的に返される結果の型

例えば Stream<Character> を単純に繋げて String にする場合は、 Stream の各 Character ( T )を StringBuilder ( A )に append した後に String ( R )に変換する、 という流れになります。

Note

高パフォーマンスを得るため中間コンテナは ミュータブル となっています。 詳細は java.util.streamパッケージの「可変リダクション」 を参照ください。

Collector インターフェースの説明

collectメソッド は引数に Collector を取ります。 Collector は「関数を返す4つのメソッド」と「特性を返すメソッド」を持ったインターフェースです。

「特性」については後述するとして、まず「4つの関数」を説明します。

  • supplier : 中間コンテナを生成する関数。 順次処理のとき最初の1回だけ実行される。 並列処理のときは複数回実行されることがある。
  • accumulator : 中間コンテナへ値を折り畳む関数。 Stream の要素の数だけ実行される。
  • combiner : ふたつの中間コンテナをひとつにマージする関数。 並列処理のときに実行されることがある。
  • finisher : 中間コンテナから最終的な結果へ変換する。 最後の1回だけ実行される。

Note

日本語Javadocの説明文ではそれぞれ「サプライヤ」「アキュムレータ」「コンバイナ」「フィニッシャ」と表記されています。 勉強会などで読み方を牽制し合わなくて済みますね!

文字を結合する Collector の例

例えば CharacterStreamStringBuilder へ折り畳んで最終的に String に変換するという処理を考えてみます。

Collector が返す関数はそれぞれ次のような処理を行うようにします。

  • supplierStringBuilder のインスタンスを生成する
  • accumulatorStringBuilderCharacterappend する
  • combiner でふたつの StringBuilder をひとつにマージする
  • finisherStringBuildertoString する

各関数のコードを記載します。

  • supplier

    引数なしで StringBuilder のインスタンスを返します。

    () -> new StringBuilder()
    

    またはコンストラクタ参照でも良いです。

    StringBuilder::new
    
  • accumulator

    StringBuilderCharacter を受け取って append します。 戻り値は void です。

    (sb, c) -> sb.append(c)
    

    またはメソッド参照でも良いです。

    StringBuilder::append
    
  • combiner

    ふたつの StringBuilder を受け取ってひとつの StringBuilder にマージして返します。

    (sb1, sb2) -> sb1.append(sb2);
    

    またはメソッド参照でも良いです。

    StringBuilder::append
    
  • finisher

    StringBuilder を受け取って String へ変換して返します。

    sb -> sb.toString()
    

    またはメソッド参照でも良いです。

    StringBuilder::toString
    

これら4つの関数をもとにして Collector インスタンスを生成します。 愚直に Collector インターフェースを実装したクラスを作っても良いのですが Collectorofメソッド を利用するのが楽です。

Collector<Character, StringBuilder, String> characterJoiner =
        Collector.of(() -> new StringBuilder(),     //supplier
                     (sb, c) -> sb.append(c),       //accumulator
                     (sb1, sb2) -> sb1.append(sb2), //combiner
                     sb -> sb.toString()));         //finisher

//コンストラクタ参照・メソッド参照バージョン
Collector<Character, StringBuilder, String> characterJoiner =
        Collector.of(StringBuilder::new,        //supplier
                     StringBuilder::append,     //accumulator
                     StringBuilder::append,     //combiner
                     StringBuilder::toString)); //finisher

この Collector を使って文字を連結してみます。

String s = Stream.of('h', 'e', 'l', 'l', 'o').collect(characterJoiner);
System.out.println(s); //hello

Collector の特性

Collector はネストした列挙型 Characteristics を使用してみっつの特性を表すことができます。 各特性について説明します。

  • CONCURRENT : ひとつの結果コンテナインスタンスに対して複数スレッドから accumulator を実行できる特性です。

    つまり次のような処理を行っても不整合が起こらなければ、この特性を持っていると言えます。

    A acc = supplier.get(); //中間コンテナ
    
    new Thread(() -> accumulator.accept(acc, t1)).start();
    
    new Thread(() -> accumulator.accept(acc, t2)).start();
    
  • IDENTITY_FINISH : finisher が恒等関数であり、省略できる特性です。

    つまり finisher が次のような実装になる場合、この特性を持っていると言えます。

    Function<A, R> finisher = a -> (R) a;
    
  • UNORDERED : 操作が要素の順序に依存しない特性です。

いずれの特性も性能向上のためのものと思われます。 ですので特性をひとつも持たないとしても致命的な問題は無さそうです。 むしろ自作 Collector がどの特性を持っているか分からない、いまいち自信が無いなどの場合は Characteristics を設定しない方が良いかも知れませんね。

Collector インスタンスを生成する際に特性を与えたい場合は of メソッドの第5引数(可変長引数です)を使用します。

Collector<T, A, R> collector =
        Collector.of(supplier, accumulator, combiner, finisher,
                     Characteristics.CONCURRENT,
                     Characteristics.IDENTITY_FINISH,
                     Characteristics.UNORDERED);

中間コンテナの型変数について

Collector は自分で実装しても良いですが、よく使われそうな実装を返す static メソッドを多数定義した Collectors というユーティリティクラスが提供されています。

Collectors のメソッド一覧を眺めて戻り値に注目するとほとんどが Collector<T, ?, R> となっており、 中間コンテナの型がワイルドカードで宣言されていることが分かります。

冒頭でも書きましたが StreamcollectメソッドStream<T>R に変換する操作です。 このときの TRCollector<T, A, R> のそれに対応します。 つまり collectメソッド を使うひと―― Collector の利用者――にとっては中間コンテナが何であるか意識する必要はないんですね。

このように利用者には不要な中間コンテナの型が見えており、 実際にはワイルドカードが宣言されているというのは少し残念であり、 collectメソッド をややこしく感じさせている一因かも知れないな、と思います。

というわけで Collectors の各メソッドでのワイルドカードは空気のように扱うことにしましょう。

まとめ、それと自分への宿題

  • 使う側としては中間コンテナの存在は無視る
  • よく分からんかったら Characteristics は付与しない
  • 何はともあれ collectメソッド 便利

こっから宿題。

  • Scalaの scan みたいなやつを実装してみる。

    こんなやつです。

    //これはScalaコード
    val xs = 1 to 5 toList
    xs.scan(0)(_ + _) //0, 1, 3, 6, 10, 15
    

追記:宿題やった

]]>
Sat, 04 Oct 2014 00:00:00 +0000
https://backpaper0.github.io/2014/10/04/stream_methods.html https://backpaper0.github.io/2014/10/04/stream_methods.html <![CDATA[Streamのメソッドを操作の種類別で一覧にした]]> Streamのメソッドを操作の種類別で一覧にした

Stream の操作は 中間操作終端操作 がありますが、 各メソッドがどちらの操作にあたるのか一覧にしてみました。

Note

中間操作と終端操作の詳細は ストリーム操作とパイプライン を参照してください。

ちなみに一覧中の TStream が取る型変数です。 例えば String[] をストリーム化した場合 TString になります。

それから SBaseStream が取る型変数で Stream<T> を指します。

その他(中間操作・終端操作のどちらにも分類されないもの)

]]>
Sat, 04 Oct 2014 00:00:00 +0000
https://backpaper0.github.io/2014/09/30/jersey_rx_client.html https://backpaper0.github.io/2014/09/30/jersey_rx_client.html <![CDATA[Jersey ClientのRxサポートを軽〜く試す]]> Jersey ClientのRxサポートを軽〜く試す

Jersey 2.13がリリースされました。

リリースノートを見ると [JERSEY-2639] - Jersey Client - Add Support for Rx というのがあったので試してみましたん。

pom.xmlに突っ込むdependency

jersey-rx-clientの実装 には、

  • jersey-rx-client-guava
  • jersey-rx-client-java8
  • jersey-rx-client-jsr166e
  • jersey-rx-client-rxjava

があるっぽいですがRxJavaとかよく分かんないので今回はjava8で試します。

<dependency>
  <groupId>org.glassfish.jersey.ext.rx</groupId>
  <artifactId>jersey-rx-client-java8</artifactId>
</dependency>

クライアントコード

名前を渡したらこんにちは言ってくれるいつものリソースクラスがあったとします。

まずはふつうのJAX-RSクライアントのコード。

WebTarget target = ClientBuilder.newClient().target("http://localhost:8080/rest/hello");

String resp = target.queryParam("name", "world")
                    .request()
                    .get(String.class);

System.out.println(resp); //Hello, world!

うむ。普通。

次にRx板のコードです。

WebTarget target = ClientBuilder.newClient().target("http://localhost:8080/rest/hello");

CompletionStage<String> stage = RxCompletionStage.from(target)
                                    .queryParam("name", "world")
                                    .request()
                                    .rx()
                                    .get(String.class);

stage.thenAccept(s -> System.out.println(s)); //Hello, world!

ご覧の通り CompletionStage であれこれできるっぽいです。

まとめ

RxJava学ぼうかな。

]]>
Tue, 30 Sep 2014 00:00:00 +0000
https://backpaper0.github.io/2014/09/26/nullpo.html https://backpaper0.github.io/2014/09/26/nullpo.html <![CDATA[ぬるぽ]]> ぬるぽ

あなたとぬるぽ。

拡張forループ

int[] xs = null;
for (int x : xs) {
}

配列・リストが null なら拡張forループでぬるぽっ!

アンボクシング

Integer x = null;
int y = x;

プリミティブラッパーをプリミティブにアンボクシングするときにぬるぽっ!

普通に数字の足し算とかしててぬるぽが出たらこれを疑います。

throw

UnsupportedOperationException e = null;
throw e;

nullthrow したら投げられる例外はぬるぽっ!

String switch

String x = null;
switch (x) {
}

String のswitch文でぬるぽっ!

try with resources

try (AutoCloseable x = null) {
}

try with resourcesで AutoCloseablenull なら close するときにぬるぽっ!には ならない

close の前に null チェックするようにコンパイルされます。

コンストラクタ

new Hoge(a -> a.x.length());

何の変哲も無いコンストラクタですが、ぬるぽっ!になるケースがあります。

class Hoge {

    final String x;

    public Hoge(Consumer<Hoge> c) {
        c.accept(this);
        this.x = "hoge";
    }
}

フィールド x はfinalなのにぬるぽになるというアレです。 コンストラクタ終わってないインスタンスはメソッドに渡さないでおきましょー。

メソッド実行

Hoge x = null;
x.foobar();

ぬるぽっ! にはならないケースがあります

これ。

class Hoge {

    static void foobar() {
    }
}

まあ実際はこんなコードに出会うことは無いでしょう。

無いでしょう。

本日のコード

]]>
Fri, 26 Sep 2014 00:00:00 +0000
https://backpaper0.github.io/2014/09/14/kotlinkansai.html https://backpaper0.github.io/2014/09/14/kotlinkansai.html <![CDATA[関西Kotlin勉強会やった #kotlin]]> 関西Kotlin勉強会やった #kotlin

東京からKotlinエバンジェリスト(自称)の たろうさん をお招きして神戸でKotlinの勉強会をだらだらやったった。

私の発表

要約すると、

  • KotlinでJAX-RSは普通にできるけれどKotlinの良さを活かしきれないのがちょっとくやしい
  • JVM言語は最終的にはjavapすればアレ
  • しょぼちむネタにしてごめんな、今度ごはんおごるわー

という感じです。

こざけさんのLT

関西Kotlin勉強会の一週間前にScalaMatsuriでたろうさんに「LTやってくださいよ」と無茶振りされた こざけさん のLT。

お金がnullなのはいけないと思いますが、お金をnullにしたのはご自身なのでアレですね。

たろうさんの発表

過去のスライドからいくつかを選んで大事な部分をかいつまんで話してくれました。

まったりした雰囲気だったとはいえ2時間ほどノンストップでお話してくださったのはすごかった。

すごい勢いでKotlinを吸収できた気がするのである程度定着するよう復習しておかねばなりません。

まとめ

  • Kotlin面白い
  • でもREPLはよく落ちるから Kotlin Web Demo で遊ぶと良い
  • イベント後、店変えつつ5時間ぐらい飲み食いしててイベントよりも長いやんけ的な
  • その飲み食いも込みでめちゃくちゃ楽しかった
]]>
Sun, 14 Sep 2014 00:00:00 +0000
https://backpaper0.github.io/2014/08/27/wildflykansai.html https://backpaper0.github.io/2014/08/27/wildflykansai.html <![CDATA[関西WildFly勉強会をやった #wildflykansai]]> 関西WildFly勉強会をやった #wildflykansai

やりました。

@nekopさん にお越し頂いてたっぷりと喋って貰って大満足でした!

というか私はお昼ごはんを一緒させて貰った時点でかなり満足した。

ワタクシのスライドん。

]]>
Wed, 27 Aug 2014 00:00:00 +0000
https://backpaper0.github.io/2014/08/02/glassfish_admingui.html https://backpaper0.github.io/2014/08/02/glassfish_admingui.html <![CDATA[GlassFish 3.1.2.2の管理コンソールでlocalhostをプロキシ経由しようとしていて困ったけど解決した話]]> GlassFish 3.1.2.2の管理コンソールでlocalhostをプロキシ経由しようとしていて困ったけど解決した話

現象

ローカルでGlassFish 3.1.2.2を動かして管理コンソールを開こうと思ったら開けなくて困りました。

具体的には次のような症状です。

  • デフォルトのドメイン domain1 で管理コンソールの起動にかなり時間がかかる。
  • domain1 は管理パスワードが設定されていないのでログインなしで管理コンソールを開けるはずなのにログインフォームが表示される。
  • ユーザー名だけ入力してログインしようとするとかなり時間がかかった挙げ句ログインできない。

環境

  • GlassFish 3.1.2.2
  • JDK 1.7.0_65
  • Windows 7
  • ネットワークの設定でプロキシ設定済み

調査

まず管理コンソールの起動に時間がかかっている際のスレッドダンプを取得することにしました。

GlassFishを起動して http://localhost:4848 をブラウザで開きます。

管理コンソールがなかなか起動されないことを確認したあと JVisualVMでGlassFishに接続してボタンぽちっとスレッドダンプ取得しました。 JVisualVM便利。

スレッドダンプをエディタにコピペして glassfish という文字列を検索してそれっぽいスレッドを探しました。

その結果、次に記載するスレッドが怪しそうでした。 ソケットからのデータ読み込み中で止まってる感じがします。

"admin-thread-pool-4848(3)" daemon prio=6 tid=0x000000000a861800 nid=0xb08 runnable [0x000000001275c000]
   java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:152)
    at java.net.SocketInputStream.read(SocketInputStream.java:122)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
    - locked <0x00000000f7a63a80> (a java.io.BufferedInputStream)
    at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:687)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:633)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1323)
    - locked <0x00000000f7a47030> (a sun.net.www.protocol.http.HttpURLConnection)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:240)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:147)
    at com.sun.jersey.api.client.filter.CsrfProtectionFilter.handle(CsrfProtectionFilter.java:97)
    at com.sun.jersey.api.client.Client.handle(Client.java:648)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:670)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:503)
    at org.glassfish.admingui.common.util.RestUtil.get(RestUtil.java:755)
    at org.glassfish.admingui.common.util.RestUtil.restRequest(RestUtil.java:191)
    at org.glassfish.admingui.common.handlers.RestApiHandlers.restRequest(RestApiHandlers.java:223)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.sun.jsftemplating.layout.descriptors.handler.Handler.invoke(Handler.java:442)
    at com.sun.jsftemplating.layout.descriptors.LayoutElementBase.dispatchHandlers(LayoutElementBase.java:420)
    at com.sun.jsftemplating.layout.descriptors.LayoutElementBase.dispatchHandlers(LayoutElementBase.java:394)
    at com.sun.jsftemplating.layout.descriptors.LayoutComponent.beforeCreate(LayoutComponent.java:348)
    at com.sun.jsftemplating.layout.descriptors.LayoutComponent.getChild(LayoutComponent.java:288)
    at com.sun.jsftemplating.layout.LayoutViewHandler.buildUIComponentTree(LayoutViewHandler.java:556)
    at com.sun.jsftemplating.layout.LayoutViewHandler.createView(LayoutViewHandler.java:255)
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:247)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
    at org.apache.catalina.core.ApplicationDispatcher.doInvoke(ApplicationDispatcher.java:809)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:671)
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:505)
    at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:476)
    at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:355)
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:305)
    at org.glassfish.admingui.common.security.AdminConsoleAuthModule.validateRequest(AdminConsoleAuthModule.java:232)
    at com.sun.enterprise.security.jmac.config.GFServerConfigProvider$GFServerAuthContext.validateRequest(GFServerConfigProvider.java:1171)
    at com.sun.web.security.RealmAdapter.validate(RealmAdapter.java:1452)
    at com.sun.web.security.RealmAdapter.invokeAuthenticateDelegate(RealmAdapter.java:1330)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:551)
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:623)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
    at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
    at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
    at java.lang.Thread.run(Thread.java:745)

JerseyクライアントでHTTPリクエストを行っているようです。

RestApiHandlers.java:223 にブレークポイントを置いてデバッグしてみることにしました。 デバッグにはEclipseを使いました。

一旦ドメインを停止してデバッグモードで起動し直します。

asadmin start-domain --debug domain1

起動したらコンソールにデバッグポートが表示されるのでそれを参考にリモートデバッグします。 Eclipseのデバッグの設定から Remote Java Application を選んで実行します。

デバッグ用に適当にプロジェクトを作りました。 dependency は適当に使ってそうなやつを突っ込んでおきました。

<dependencies>
  <dependency>
    <groupId>org.glassfish.main.admingui</groupId>
    <artifactId>console-core</artifactId>
    <version>3.1.2.2</version>
  </dependency>
  <dependency>
    <groupId>org.glassfish.main.admingui</groupId>
    <artifactId>console-common</artifactId>
    <version>3.1.2.2</version>
  </dependency>
</dependencies>

Note

ちなみにNetBeansはこんな面倒なことをしなくてもプロジェクトをデバッグ実行すれば自動でGlassFishもデバッグモードで起動したと思います。

そんな感じでデバッグしてみたところ http://localhost:4848/management/domain/anonymous-user-enabled へのGETリクエストがなかなか返って来ませんでした。

やっとこさ返ってきたレスポンスは503エラーでした。 エンティティボディを見るとプロキシから応答が無いなどと書かれており HTTPリクエストがプロキシ経由になっているのがマズいようでした。

対応

localhost をプロキシを通過する対象から外してみました。

asadmin create-jvm-options -Dhttp.nonProxyHosts=localhost

GlassFishを再起動して管理コンソールにアクセスすると問題なく起動してくれました。

というわけで当座の問題は解決しました。

疑問

Windowsのプロキシ設定には “ローカル アドレスにはプロキシ サーバーを使用しない” というチェックボックスがありますが、 これにチェック入れてもJavaのソケットAPIは localhost を除外してくれないのでしょうか?

教えてエロいひと!

]]>
Sat, 02 Aug 2014 00:00:00 +0000
https://backpaper0.github.io/2014/07/21/jersey_standalone.html https://backpaper0.github.io/2014/07/21/jersey_standalone.html <![CDATA[Jerseyをjava -jarで動かす]]> Jerseyをjava -jarで動かす

Jerseyはサーブレット経由でなく com.sun.net.httpserver.HttpServerGrizzly 、Jettyで動かす事もできるのでmaven-shade-pluginなどでひとつのJARにまとめてしまえば java -jar で実行できるJAX-RSアプリケーションの完成です。

例えば com.sun.net.httpserver.HttpServer を使用するやつをdependenciesに突っ込んで、

<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-jdk-http</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.glassfish.jersey</groupId>
      <artifactId>jersey-bom</artifactId>
      <version>2.10.1</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

mavne-shade-pluginを突っ込んで、

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>2.3</version>
  <executions>
    <execution>
      <id>standalone-jar</id>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <transformers>
          <transformer
            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>app.Main</mainClass>
          </transformer>
        </transformers>
       <createDependencyReducedPom>false</createDependencyReducedPom>
      </configuration>
    </execution>
  </executions>
</plugin>

JAX-RSなコードを書いて、

package app;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("hello")
public class Hello {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String say(@QueryParam("name") @DefaultValue("world") String name) {
        return String.format("Hello, %s!", name);
    }
}

メインクラス書いて、

package app;

import java.net.URI;

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

public class Main {

    public static void main(String[] args) {

        //ベースとなるURL
        URI uri = URI.create("http://localhost:8080/");

        //リソースクラスなどを登録する
        //以下は一例
        ResourceConfig config = new ResourceConfig();

        //appパッケージ以下のリソースクラスなどJAX-RSに関係するクラスを登録する
        //パッケージは再帰的にスキャンされる
        config.packages(true, "app");

        //リクエストとレスポンスに関する情報をログ出力するフィルターを登録する
        config.register(LoggingFilter.class);

        //サーバー起動
        JdkHttpServerFactory.createHttpServer(uri, config);

        //http://localhost:8080/hello?name=foobar にアクセスして動作確認
        //control + cでJVM落としてサーバも停止する
    }
}

mvn package でJAR作って java -jar hoge.jar で動かしましょう。

ギッハブにもサンプル置いています。

Gradleではどうやったら良いんでしょうね? 誰か書いて下さいお願いします。

]]>
Mon, 21 Jul 2014 00:00:00 +0000
https://backpaper0.github.io/2014/07/06/kotlin_default_parameter.html https://backpaper0.github.io/2014/07/06/kotlin_default_parameter.html <![CDATA[Kotlinのデフォルト引数を調べた]]> Kotlinのデフォルト引数を調べた

こっとりーん!(挨拶)

Kotlinはこんな感じでデフォルト引数が使えます。

class Calc {
  fun add(x: Int = 1, y: Int = 2): Int = x + y
  fun add2() = add(8, 16)
  fun add3() = add(4)
  fun add4() = add()
}

addの引数にデフォルト値を設定しているのでadd3やadd4で引数を省略できています。

で、これをjavapしました。

public final int add(int, int);
  descriptor: (II)I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=2, locals=3, args_size=3
       0: iload_1
       1: iload_2
       2: iadd
       3: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       4     0  this   LCalc;
          0       4     1     x   I
          0       4     2     y   I
    LineNumberTable:
      line 2: 0
  RuntimeVisibleParameterAnnotations:
    parameter 0:
      0: #22(#23=s#24)
    parameter 1:
      0: #22(#23=s#25)

public static int add$default(Calc, int, int, int);
  descriptor: (LCalc;III)I
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=4, locals=4, args_size=4
       0: aload_0
       1: iload_3
       2: iconst_1
       3: iand
       4: ifeq          9
       7: iconst_1
       8: istore_1
       9: iload_1
      10: iload_3
      11: iconst_2
      12: iand
      13: ifeq          18
      16: iconst_2
      17: istore_2
      18: iload_2
      19: invokevirtual #32                 // Method add:(II)I
      22: ireturn
    LineNumberTable:
      line 2: 7
    StackMapTable: number_of_entries = 2
         frame_type = 73 /* same_locals_1_stack_item */
        stack = [ class Calc ]
         frame_type = 255 /* full_frame */
        offset_delta = 8
        locals = [ class Calc, int, int, int ]
        stack = [ class Calc, int ]


public final int add2();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=3, locals=1, args_size=1
       0: aload_0
       1: bipush        8
       3: bipush        16
       5: invokevirtual #32                 // Method add:(II)I
       8: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       9     0  this   LCalc;
    LineNumberTable:
      line 3: 0

public final int add3();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=4, locals=1, args_size=1
       0: aload_0
       1: iconst_4
       2: iconst_0
       3: iconst_2
       4: invokestatic  #37                 // Method add$default:(LCalc;III)I
       7: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  this   LCalc;
    LineNumberTable:
      line 4: 0

public final int add4();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=4, locals=1, args_size=1
       0: aload_0
       1: iconst_0
       2: iconst_0
       3: iconst_3
       4: invokestatic  #37                 // Method add$default:(LCalc;III)I
       7: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  this   LCalc;
    LineNumberTable:
      line 5: 0

public Calc();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokespecial #41                 // Method java/lang/Object."<init>":()V
       4: return
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LCalc;

コンスタントプールやコンストラクタは省略しました。

注目すべきはadd$default(Calc, int, int, int)というstaticメソッドですね。 第2,3引数はaddメソッドの第1,2引数に対応しています。

そして第4引数がデフォルト値が必要かどうかを判断するためのビット演算を利用したフラグです。 判断の方法ですが、例えば第4引数と1との論理積が0の場合は第1引数にデフォルト値を、第4引数と2との論理積が0の場合は第2引数にデフォルト値を使用する、といった具合です。 次に示す箇所がそれに当たります。

1: iload_3
2: iconst_1
3: iand
4: ifeq          9
7: iconst_1
8: istore_1
9: iload_1

intは32ビットなので33以上の引数にはどう対応してるのだろうと思って調べてみたところ第33引数では一周回って1との論理積で判断していました。 ということは第1引数に明示的に値を指定すると第33引数に何も指定していなくてもデフォルト値は使用されないということになりますね。 試してはいないですが。

javapの抜粋を掲載しますがインデックス416以降がそれに当たります。

390: iload         34
392: ldc           #77                 // int 1073741824
394: iand
395: ifeq          401
398: iconst_1
399: istore        31
401: iload         31
403: iload         34
405: ldc           #78                 // int -2147483648
407: iand
408: ifeq          414
411: iconst_1
412: istore        32
414: iload         32
416: iload         34
418: iconst_1
419: iand
420: ifeq          426
423: iconst_1
424: istore        33
426: iload         33
428: invokevirtual #80                 // Method x:(IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII)I
431: ireturn

ただしKotlinはまだM8なので改善されるかも知れませんね。 ていうか33も引数使うんじゃねえ、って話ですが。

そんなこんなでKotlinのデフォルト引数を調べてみました。 あとやっぱりjavapは楽しいです。

]]>
Sun, 06 Jul 2014 00:00:00 +0000
https://backpaper0.github.io/2014/07/01/kotlin_jaxrs.html https://backpaper0.github.io/2014/07/01/kotlin_jaxrs.html <![CDATA[KotlinではじめるJAX-RS]]> KotlinではじめるJAX-RS

こっとりーん!(挨拶)

説明はもろもろすっ飛ばしてリソースクラスのコード掲載します。

package app

import javax.ws.rs.core.MediaType
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.QueryParam as q

Path("hello")
Produces(MediaType.TEXT_PLAIN)
class Hello {

    GET fun get(q("name") name: String): String = "Hello, ${name}!"
}

という感じで普通にJAX-RSできました。

というかnameがnull許容しないので@DefaultValueを付けるべきと今思いましたので気が向いたら直しておきます(やらないパターン)。

サンプルが簡易すぎてKotlinの良さが出ていないのが悲しいですね。

良かった点。

  • Mavenで簡単にビルドできた
  • MavenプロジェクトをIntellij IDEAで容易くインポートできた
  • たろーさん とTwitterで絡めた

微妙な点。

  • Intellij IDEAのKotlinプラグインのOrganize Importが弱い気がする
  • Intellij IDEAのコードフォーマットが option + command + l で両手使うのがやだ
  • Intellij IDEAで command + w でファイルが閉じてくれなかった

そんな感じです。 要するにIntellij IDEAに慣れていないだけ、と。

それと テストクラス はJavaで書いていますが、これはアノテーション付きのstaticメソッドの書き方が分からなかったからです。 きっと誰かがKotlinに直してプルリクしてくれるに違いない(チラッ

本日のコードはGitHubにあります。 Arquillian のwildfly-managedでテスト書いてます。 テスト実行すると多くのJARと WildFly 8.1.0.Final をダウンロードするので時間のあるときにどうぞ。

Kotlinの勉強会もあるみたいですよ。

]]>
Tue, 01 Jul 2014 00:00:00 +0000
https://backpaper0.github.io/2013/12/30/javafx.html https://backpaper0.github.io/2013/12/30/javafx.html <![CDATA[JavaFXでクラサバする事を考えた]]> JavaFXでクラサバする事を考えた

私はエスアイヤーでギョームアプリ作ってるのでその辺りにJavaFXぶっ込んだらどうなるか考えてみました。

とりあえず次に挙げた機能が必要っぽいかなーと思います。

  • 画面遷移
  • 検索結果などのグリッド表示
  • グリッドから詳細画面を開く的なやつ
  • ダイアログ
  • バックグラウンド処理
  • サーバとの通信

画面遷移

Hoge駆動の忘年会というぁゃしぃ集まりでは

という案を挙げてみました。 このふたつのうちSceneを入れ替える方法だと画面がチラついてしまいましたので ルートノードを入れ替える方法がベターかなー、と思っていましたが、 StackPaneあたりに必要なだけNodeを突っ込んでvisibleを切り替える、 というのがもっと良いんじゃないかと現時点では思っています。

package sample;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class ScreenTransitionSample extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        final BorderPane firstPane = new BorderPane();
        Button next = new Button("次へ行く");
        firstPane.setCenter(next);

        final BorderPane secondPane = new BorderPane();
        Button prev = new Button("前へ戻る");
        secondPane.setCenter(prev);
        secondPane.setVisible(false);

        StackPane root = new StackPane();
        root.getChildren().add(firstPane);
        root.getChildren().add(secondPane);

        next.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                firstPane.setVisible(false);
                secondPane.setVisible(true);
            }
        });
        prev.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                firstPane.setVisible(true);
                secondPane.setVisible(false);
            }
        });

        Scene scene = new Scene(root, 400, 300);
        stage.setScene(scene);
        stage.setTitle("画面遷移");
        stage.show();
    }
}

検索画面などのグリッド表示

TableView を使います。

Scene Builderを使って画面を組み立てる場合はまずTableViewをペタっと置いて、 カラムを足す場合はTableColumnを貼付けたTableViewへペロっと置きます。

データを表示するときは TableColumn#setCellValueFactory(Callback) を使って値のファクトリーを設定して、 TableViewのitemsへaddします。

package sample;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class GridController implements Initializable {

    @FXML
    private TableView<Account> accounts;
    @FXML
    private TableColumn<Account, String> id;
    @FXML
    private TableColumn<Account, String> name;
    @FXML
    private TableColumn<Account, String> desc;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        id.setCellValueFactory(new PropertyValueFactory<Account, String>("id"));
        name.setCellValueFactory(new PropertyValueFactory<Account, String>("name"));
        desc.setCellValueFactory(new PropertyValueFactory<Account, String>("desc"));

        Account account1 = Account.newInstance("backpaper0", "うらがみ", "全裸");
        Account account2 = Account.newInstance("tan_go238", "たんご", "カレー");
        Account account3 = Account.newInstance("irof", "いろふ", "足首");

        accounts.getItems().add(account1);
        accounts.getItems().add(account2);
        accounts.getItems().add(account3);
    }
}

こんな感じになります。

../../../_images/GridSample.png

グリッドから詳細画面を開く的なやつ

TableColumn#setCellFactory(Callback) を使います。 Callback実装クラスではセル毎にcallメソッドが呼ばれるようですが、 ここでTableCellを作成して返します。 TableCellではsetGraphicメソッドでボタンをセットしています。 これでセルにボタンを置く事が出来るようです。

public class GridController implements Initializable {

    ...

    @FXML
    private TableColumn<Account, String> opener;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        opener.setCellFactory(new OpenerFactory());

        opener.setCellValueFactory(new PropertyValueFactory<Account, String>("id"));

        ...
    }
}

class OpenerFactory implements Callback<TableColumn<Account, String>, TableCell<Account, String>> {

    @Override
    public TableCell<Account, String> call(TableColumn<Account, String> param) {
        TableCell<Account, String> tableCell = new TableCell<Account, String>() {

            private Pane pane = createPane();

            private String id;

            private Pane createPane() {
                HBox pane = new HBox();
                pane.setAlignment(Pos.CENTER);

                Button button = new Button("詳細を開く");
                button.setOnAction(new EventHandler<ActionEvent>() {

                    @Override
                    public void handle(ActionEvent event) {
                        System.out.println(id);
                    }
                });
                pane.getChildren().add(button);
                return pane;
            }

            @Override
            protected void updateItem(String id, boolean empty) {
                super.updateItem(id, empty);
                if (empty == false) {
                    this.id = id;
                    setGraphic(pane);
                }
            }
        };
        return tableCell;
    }
}

ボタンが置けました。

../../../_images/GridOpenerSample.png

ダイアログ

Swingで言うところのJOptionPaneのようなお手軽ダイアログは無いようですが、Stageを使う事で実現可能っぽいです。

バックグラウンド処理

Service を使います。 SwingWorker的なやつです。 こんな感じの雰囲気で。

Service<String> service = new Service<String>() {

    @Override
    protected Task<String> createTask() {
        Task<String> task = new Task<String>() {

            @Override
            protected String call() throws Exception {
                TimeUnit.SECONDS.sleep(5);
                return "終わったよーん";
            }

            @Override
            protected void succeeded() {
                text.setText(getValue());
            }
        };
        return task;
    }
};
service.start();

Service#createTask()でTaskを返しています。 Taskではcallメソッドを実装していますが、これがSwingWorkerでいうdoInBackgroundのようです。 succeededメソッドは処理が正常終了したときに呼ばれます。 他にキャンセルしたときに呼ばれるcancelledメソッドや失敗したときに呼ばれるfailedメソッドがあります。

しかし結果がどうあれ必ず呼ばれるfinally的なメソッドがありません。 これは不便な気がします。

finally的なアレを実現する方法として今んところ思いついているのは、Taskには実行中かそうでないかを表すrunningというbooleanのプロパティがあるので、 それにリスナーを追加します。

task.runningProperty().addListener(
        new ChangeListener<Boolean>() {

            @Override
            public void changed(ObservableValue<? extends Boolean> observable,
                                Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    System.out.println("終わったよーん");
                }
            }
        });

もっと良い方法があったら教えて欲しいです。

あと、処理中にProgressIndicatorというのを表示しておくと良い感じになりそうです。 StackPaneにメインとなるPaneとProgressIndicatorを含んだPaneを突っ込んでvisibleで切り替えます。

というわけでバックグラウンド処理のサンプルを次に記載します。

package sample;

import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class LongTimeTaskSample extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        //インジケータを含むPaneを組み立てる
        final GridPane progressPane = new GridPane();
        progressPane.setAlignment(Pos.CENTER);

        ProgressIndicator indicator = new ProgressIndicator();
        progressPane.add(indicator, 0, 0);

        //メインとなるPaneを組み立てる
        GridPane mainPane = new GridPane();
        mainPane.setAlignment(Pos.CENTER);
        mainPane.setHgap(10);
        mainPane.setVgap(10);

        Button button = new Button("重い処理を行う");
        mainPane.add(button, 0, 0);

        final Text text = new Text();
        mainPane.add(text, 0, 1);

        //StackPaneに突っ込む
        StackPane root = new StackPane();
        root.getChildren().add(mainPane);
        root.getChildren().add(progressPane);

        //最初はインジケータは見えなくする
        progressPane.setVisible(false);

        button.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {

                //ボタンが押されたらインジケータを見せる
                progressPane.setVisible(true);
                text.setText("");
                Service<String> service = new Service<String>() {

                    @Override
                    protected Task<String> createTask() {
                        Task<String> task = new Task<String>() {

                            @Override
                            protected String call() throws Exception {
                                TimeUnit.SECONDS.sleep(5);
                                return "終わったよーん";
                            }
                        };
                        task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {

                            @Override
                            public void handle(WorkerStateEvent event) {
                                //タスクが終わったらインジケータを見えなくする
                                progressPane.setVisible(false);
                                text.setText(getValue());
                            }
                        });
                        return task;
                    }
                };
                service.start();
            }
        });

        Scene scene = new Scene(root, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.setTitle("重い処理");
        primaryStage.show();
    }
}

実行したらこんな感じです。

ポチっと。

../../../_images/LongTimeTaskSample1.png

くるくるー。

../../../_images/LongTimeTaskSample2.png

どーん!

../../../_images/LongTimeTaskSample3.png

ちなみにこのコードだとインジケータが表示されているときにボタンはクリックできなくなっていますが、 タブでフォーカス移動してスペースキーで押せたりします。 きっとjava.awt.FocusTraversalPolicyのようなものがあると思うのでまた勉強しておくことにします。

サーバとの通信

( ゚∀゚)o彡°JAX-RS!JAX-RS!

まとめ

ここ数日JavaFXを触ってみてエスアイヤーのギョームアプリも普通に書けそうだなー、と感じました。

また今回はフォーカスしませんでしたがFXMLで画面を組めるのがすごく良いですね。 Scene Builderでサクッとモックを作って、OKならそのまま実装する、というスタイルが楽にできそうで嬉しいです。

あとScene Builderが特定のIDEに依存していないのも嬉しいですね。 好きなIDEを使えます。

という訳でJava 8がリリースされたら是非ともJavaFXでアプリケーション組みたいなー、と思ったのでした。

おわり。

参考資料

]]>
Mon, 30 Dec 2013 00:00:00 +0000
https://backpaper0.github.io/2013/12/22/visitor.html https://backpaper0.github.io/2013/12/22/visitor.html <![CDATA[Visitorパターンについて考えた]]> Visitorパターンについて考えた

というお話。縦に長いです。コードが。

ポリもーなんとかでなんとかする

例えば数値を表すNumNode、足し算を表すAddNode、それらのインターフェースとなるNodeがあるとします。 で、計算を実装する場合Nodeにcalcメソッドとか定義してNumNodeとAddNodeで実装します。

package visitor;

public interface Node1 {

    int calc();
}

class NumNode1 implements Node1 {

    public final int value;

    public NumNode1(int value) {
        this.value = value;
    }

    @Override public int calc() {
        return value;
    }
}

class AddNode1 implements Node1 {

    public final Node1 left;
    public final Node1 right;

    public AddNode1(Node1 left, Node1 right) {
        this.left = left;
        this.right = right;
    }

    @Override public int calc() {
        return left.calc() + right.calc();
    }
}

こいつで2 + 3 + 4を計算する場合は次のように使います。

package visitor;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import org.junit.Test;

public class Node1Test {

    @Test public void testCalc() throws Exception {
        Node1 node1 = new NumNode1(2);
        Node1 node2 = new NumNode1(3);
        Node1 node3 = new AddNode1(node1, node2);
        Node1 node4 = new NumNode1(4);
        Node1 node5 = new AddNode1(node3, node4);
        int actual = node5.calc();
        int expected = 2 + 3 + 4;
        assertThat(actual, is(expected));
    }
}

さて、今度は組み立てたNodeをWriterへ書き出したくなったとします。 Nodeへprintメソッドを追加します。

package visitor;

import java.io.IOException;
import java.io.Writer;

public interface Node2 {

    int calc();

    void print(Writer out) throws IOException;
}

class NumNode2 implements Node2 {

    public final int value;

    public NumNode2(int value) {
        this.value = value;
    }

    @Override public int calc() {
        return value;
    }

    @Override public void print(Writer out) throws IOException {
        out.write(String.valueOf(value));
    }
}

class AddNode2 implements Node2 {

    public final Node2 left;
    public final Node2 right;

    public AddNode2(Node2 left, Node2 right) {
        this.left = left;
        this.right = right;
    }

    @Override public int calc() {
        return left.calc() + right.calc();
    }

    @Override public void print(Writer out) throws IOException {
        out.write("(");
        left.print(out);
        out.write("+");
        right.print(out);
        out.write(")");
    }
}

さて、次は××処理を追加しますのでNodeへ××メソッドを……と、まあ、 これはこれで良いんですが、処理を追加するたびにNodeおよびNode実装クラスに手を加える必要があり、それが嬉しくない状況もあります。

処理を外から渡す

Nodeから処理を取り除いて外から渡すことを考えてみます。

まずはinstanceofで分岐しつつ各Node実装クラスについて処理してみます。

package visitor;

public interface Node3 {}

class NumNode3 implements Node3 {

    public final int value;

    public NumNode3(int value) {
        this.value = value;
    }
}

class AddNode3 implements Node3 {

    public final Node3 left;
    public final Node3 right;

    public AddNode3(Node3 left, Node3 right) {
        this.left = left;
        this.right = right;
    }
}

class Calclurator3 {

    public int calc(Node3 node) {
        if (node instanceof AddNode3) {
            AddNode3 addNode3 = (AddNode3) node;
            return calc(addNode3.left) + calc(addNode3.right);
        } else if (node instanceof NumNode3) {
            NumNode3 numNode3 = (NumNode3) node;
            return numNode3.value;
        }
        throw new IllegalArgumentException(String.valueOf(node));
    }
}

わーいこれで処理を外だしデキタヨー、じゃねえよ!という感じですね。

instanceofの欠点はケースの漏れを静的に検出できないことだと思っています。 例えばこの例で言うとNode実装クラスはAddNodeとNumNodeがありますが、 NumNodeへの分岐を忘れていてもコンパイル時に気付きません。 さらに node instanceof java.util.Date とか無関係なクラスを書いていても これもコンパイル時に気付きません。

ケースの漏れを静的に検出といえばswitchがありますが、 Stringかprimitiveまたはenumしか使えないので今回の例には不適当です。

いやいやそんなのポリもーなんとかでアレすれば良いじゃん、という事で次のようなインターフェースを作ります。

interface Visitor4 {

    int visit(NumNode4 node);

    int visit(AddNode4 node);
}

これをこういう風に実装すれば……

class Calclurator4 implements Visitor4 {

    public int calc(Node4 node) {
        return visit(node);
    }

    @Override public int visit(NumNode4 node) {
        return node.value;
    }

    @Override public int visit(AddNode4 node) {
        return visit(node.left) + visit(node.left);
    }
}

華麗に解決!というわけには行きませんね。 calcメソッド内のvisit(node)やAddNodeをとるvisitメソッド内でのvisit(node.left)やvisit(node.right)では 渡しているNode実装クラスがなんなのか、コンパイル時には分かりませんので普通にコンパイルエラーです。

無理矢理コンパイルを通そうと思うとこんなコードになりました。

class Calclurator4 implements Visitor4 {

    public int calc(Node4 node) {
        if (node instanceof NumNode4) {
            return visit((NumNode4) node);
        } else if (node instanceof AddNode4) {
            return visit((AddNode4) node);
        } else {
            throw new IllegalArgumentException(String.valueOf(node));
        }
    }

    @Override public int visit(NumNode4 node) {
        return node.value;
    }

    @Override public int visit(AddNode4 node) {

        //compile error
        //return visit(node.left) + visit(node.left);

        if (node.left instanceof NumNode4) {
            NumNode4 left = (NumNode4) node.left;
            if (node.right instanceof NumNode4) {
                NumNode4 right = (NumNode4) node.right;
                return visit(left) + visit(right);
            } else if (node.right instanceof AddNode4) {
                AddNode4 right = (AddNode4) node.right;
                return visit(left) + visit(right);
            } else {
                throw new IllegalArgumentException(String.valueOf(node));
            }
        } else if (node.left instanceof AddNode4) {
            AddNode4 left = (AddNode4) node.left;
            if (node.right instanceof NumNode4) {
                NumNode4 right = (NumNode4) node.right;
                return visit(left) + visit(right);
            } else if (node.right instanceof AddNode4) {
                AddNode4 right = (AddNode4) node.right;
                return visit(left) + visit(right);
            } else {
                throw new IllegalArgumentException(String.valueOf(node));
            }
        } else {
            throw new IllegalArgumentException(String.valueOf(node));
        }
    }
}

はい、そこそこクソコードになりましたね? ていうかまたinstanceofが出てきましたし。

そこでVisitorパターンですよ

Nodeにacceptメソッドを追加して実装クラスで対応するvisitメソッドを呼ぶようにします。

package visitor;

import java.io.IOException;
import java.io.Writer;

public interface Node5 {

    int accept(Visitor5 visitor);
}

class NumNode5 implements Node5 {

    public final int value;

    public NumNode5(int value) {
        this.value = value;
    }

    @Override public int accept(Visitor5 visitor) {
        return visitor.visit(this);
    }
}

class AddNode5 implements Node5 {

    public final Node5 left;
    public final Node5 right;

    public AddNode5(Node5 left, Node5 right) {
        this.left = left;
        this.right = right;
    }

    @Override public int accept(Visitor5 visitor) {
        return visitor.visit(this);
    }
}

interface Visitor5 {

    int visit(NumNode5 node);

    int visit(AddNode5 node);
}

class Calclurator5 implements Visitor5 {

    @Override public int visit(NumNode5 node) {
        return node.value;
    }

    @Override public int visit(AddNode5 node) {
        int left = node.left.accept(this);
        int right = node.right.accept(this);
        return left + right;
    }
}

class Printer5 implements Visitor5 {

    private final Writer out;

    public Printer5(Writer out) {
        this.out = out;
    }

    @Override public int visit(NumNode5 node) {
        try {
            out.write(String.valueOf(node.value));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }

    @Override public int visit(AddNode5 node) {
        try {
            out.write("(");
            node.left.accept(this);
            out.write("+");
            node.right.accept(this);
            out.write(")");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }
}

先ほどの例とは異なりvisitメソッドを各Node実装クラスのacceptメソッド内で呼んでいるので どのvisitメソッドなのかコンパイル時に分かりますね。

これは次のように使います。

package visitor;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import java.io.StringWriter;
import org.junit.Test;

public class Node5Test {

    @Test public void testCalc() throws Exception {
        Node5 node1 = new NumNode5(2);
        Node5 node2 = new NumNode5(3);
        Node5 node3 = new AddNode5(node1, node2);
        Node5 node4 = new NumNode5(4);
        Node5 node5 = new AddNode5(node3, node4);
        Calclurator5 calclurator = new Calclurator5();
        int actual = node5.accept(calclurator);
        int expected = 2 + 3 + 4;
        assertThat(actual, is(expected));
    }

    @Test public void testPrint() throws Exception {
        Node5 node1 = new NumNode5(2);
        Node5 node2 = new NumNode5(3);
        Node5 node3 = new AddNode5(node1, node2);
        Node5 node4 = new NumNode5(4);
        Node5 node5 = new AddNode5(node3, node4);
        StringWriter out = new StringWriter();
        Printer5 printer = new Printer5(out);
        node5.accept(printer);
        String actual = out.toString();
        String expected = "((2+3)+4)";
        assertThat(actual, is(expected));
    }
}

これで処理を外に出せました。

が、visitメソッドの戻り値がintだったりそもそもPrinterでは戻り値が意味なかったりしてもやもやしますね。

ジェネリクスを使う

使いましょう。 引数と戻り値をジェネリクスでアレします。

package visitor;

import java.io.IOException;
import java.io.Writer;

public interface Node6 {

    <R, P> R accept(Visitor6<R, P> visitor, P parameter);
}

class NumNode6 implements Node6 {

    public final int value;

    public NumNode6(int value) {
        this.value = value;
    }

    @Override public <R, P> R accept(Visitor6<R, P> visitor, P parameter) {
        return visitor.visit(this, parameter);
    }
}

class AddNode6 implements Node6 {

    public final Node6 left;
    public final Node6 right;

    public AddNode6(Node6 left, Node6 right) {
        this.left = left;
        this.right = right;
    }

    @Override public <R, P> R accept(Visitor6<R, P> visitor, P parameter) {
        return visitor.visit(this, parameter);
    }
}

interface Visitor6<R, P> {

    R visit(NumNode6 node, P parameter);

    R visit(AddNode6 node, P parameter);
}

class Calclurator6 implements Visitor6<Integer, Void> {

    @Override public Integer visit(NumNode6 node, Void parameter) {
        return node.value;
    }

    @Override public Integer visit(AddNode6 node, Void parameter) {
        int left = node.left.accept(this, parameter);
        int right = node.right.accept(this, parameter);
        return left + right;
    }
}

class Printer6 implements Visitor6<Void, Writer> {

    @Override public Void visit(NumNode6 node, Writer parameter) {
        try {
            parameter.write(String.valueOf(node.value));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    @Override public Void visit(AddNode6 node, Writer parameter) {
        try {
            parameter.write("(");
            node.left.accept(this, parameter);
            parameter.write("+");
            node.right.accept(this, parameter);
            parameter.write(")");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}

これでだいぶ良い感じになってきました。

が、PrinterでIOExceptionをRuntimeExceptionへラップしてるおなじみのコードが哀愁を漂わせます。

例外もジェネリクスで

やってしまいましょう。 Visitorの定義を少し修正します。

package visitor;

import java.io.IOException;
import java.io.Writer;

public interface Node7 {

    <R, P, E extends Exception> R accept(Visitor7<R, P, E> visitor, P parameter)
            throws E;
}

class NumNode7 implements Node7 {

    public final int value;

    public NumNode7(int value) {
        this.value = value;
    }

    @Override public <R, P, E extends Exception> R accept(
            Visitor7<R, P, E> visitor, P parameter) throws E {
        return visitor.visit(this, parameter);
    }
}

class AddNode7 implements Node7 {

    public final Node7 left;
    public final Node7 right;

    public AddNode7(Node7 left, Node7 right) {
        this.left = left;
        this.right = right;
    }

    @Override public <R, P, E extends Exception> R accept(
            Visitor7<R, P, E> visitor, P parameter) throws E {
        return visitor.visit(this, parameter);
    }
}

interface Visitor7<R, P, E extends Exception> {

    R visit(NumNode7 node, P parameter) throws E;

    R visit(AddNode7 node, P parameter) throws E;
}

class Calclurator7 implements Visitor7<Integer, Void, RuntimeException> {

    @Override public Integer visit(NumNode7 node, Void parameter) {
        return node.value;
    }

    @Override public Integer visit(AddNode7 node, Void parameter) {
        int left = node.left.accept(this, parameter);
        int right = node.right.accept(this, parameter);
        return left + right;
    }
}

class Printer7 implements Visitor7<Void, Writer, IOException> {

    @Override public Void visit(NumNode7 node, Writer parameter)
            throws IOException {
        parameter.write(String.valueOf(node.value));
        return null;
    }

    @Override public Void visit(AddNode7 node, Writer parameter)
            throws IOException {
        parameter.write("(");
        node.left.accept(this, parameter);
        parameter.write("+");
        node.right.accept(this, parameter);
        parameter.write(")");
        return null;
    }
}

もうだいぶ訳の分からないクソコードとなってきた感じがしますが、 Calculatorではチェック例外を投げず、PrinterではIOExceptionを投げるような表現が できました。 お疲れ様でした。

他の言語ではどうなのか

Groovyではどのメソッドを呼ぶかは実行時に決まるのでacceptメソッドが不要です。 たしか動的ディスパッチと呼ばれていたと思います。

class AddNode {
    def left
    def right
}

class NumNode {
    def value
}

class Calculator {
    def visit(AddNode node) {
        visit(node.left) + visit(node.right)
    }
    def visit(NumNode node) { node.value }
}

def node1 = new NumNode(value: 2)
def node2 = new NumNode(value: 3)
def node3 = new AddNode(left: node1, right: node2)
def node4 = new NumNode(value: 4)
def node5 = new AddNode(left: node3, right: node4)
def calculator = new Calculator()
def actual = calculator.visit(node5)

assert actual == (2 + 3 + 4)

またScalaではパターンマッチを使えば良いです。 ケースの漏れはsealedを使えば検出可能だったと思います。

sealed trait Node
case class NumNode(value: Int) extends Node
case class AddNode(left: Node, right: Node) extends Node

def calc(node: Node): Int = node match {
  case NumNode(value) => value
  case AddNode(left, right) => calc(left) + calc(right)
}

val node1 = NumNode(2)
val node2 = NumNode(3)
val node3 = AddNode(node1, node2)
val node4 = NumNode(4)
val node5 = AddNode(node3, node4)
val actual = calc(node5)

assert(actual == (2 + 3 + 4))

それに、Nodeを分解してvalueやleft、rightを取り出したりできてvisitorパターンより超高機能です。 しかもコード短いし。 静的なアレだし。

まとめ

Javaではデータとアルゴリズムを分離するとき、

  • instanceofはケースの漏れを静的に検出できない
  • switchはString、primitive、enumしか受け付けない
  • パターンマッチが無い

などの理由によりVisitorパターンを使わざるを得ない場合があります。 しかし数あるデザインパターンの中でもVisitorパターンは理解するのが難しいように思います。 またそれなりに汎用的にしようと思うとジェネリクスを使って複雑な定義になってしまったり。

よって、使わなくて済むならそれに越した事は無く、使う場合でも本当にVisitorパターンが必要なのかしっかり検討すべきだと思います。

私も最近、色々あってVisitorパターンを使ってしまいましたが、もっと良い設計があったような気がしています。

こんなまとめでええんか?

まあいいか。

]]>
Sun, 22 Dec 2013 00:00:00 +0000
https://backpaper0.github.io/2013/12/03/javaee_advent_calendar_2013.html https://backpaper0.github.io/2013/12/03/javaee_advent_calendar_2013.html <![CDATA[私のBeanValidationの使い方(Java EE Advent Calendar 2013)]]> 私のBeanValidationの使い方(Java EE Advent Calendar 2013)

このエントリは Java EE Advent Calendar 2013 の3日目です。 昨日は @matsumana さんのご担当で JAX-RS + mustache - @matsumana の技術メモ でした。

今回はBeanValidationの自分なりの使い方をご紹介します。

その前に

BeanValidationてなんや?という方は JSR 349 の仕様を読むと良いでしょう。 200ページ超えてますが半分以上コードっぽいのでそんなにしんどくないんじゃないかと思わなくもないけどどうでしょうか?

もしくは「BeanValidation しんさん」でググると良いですよ。

本題

BeanValidationではフィールドやgetterに@NotNullとか@Sizeとかアノテーションをモリモリ付けてバリデーションするわけですが、調子に乗ってるとすぐアノテーション地獄になってキツいのです。 ですので特定のバリデーションを集約する方法が欲しいわけでして、正攻法は独自のアノテーションを導入してそこに集約することだと思いますが、私は別のやり方を採用しています。

まず、正攻法と同じく独自のアノテーションとConstraintValidatorを導入します。 アノテーションはこんな感じ。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { DomainValidator.class })
public @interface DomainType {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        DomainType[] value();
    }
}

至って普通ですね。

続いてConstraintValidatorはこんな感じです。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DomainValidator implements
        ConstraintValidator<DomainType, WithValidation> {

    @Override
    public void initialize(DomainType constraintAnnotation) {
    }

    @Override
    public boolean isValid(WithValidation value,
            ConstraintValidatorContext context) {

        if (value == null) {
            return true;
        }

        String message = value.validate();
        if (message == null) {
            return true;
        }

        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message)
                .addConstraintViolation();

        return false;
    }
}

isValidメソッドでは具体的なバリデーションは行わずWithValidation#validateに任せています。

WithValidation実装クラスは例えばこんな感じ。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

public class UserId implements WithValidation {

    private final String value;

    public UserId(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String validate() {

        if (value.length() > 10) {
            return "10文字以下でオナシャス";
        }

        for (char c : value.toCharArray()) {
            if (('a' <= c && c <= 'z') == false
                    && ('A' <= c && c <= 'Z') == false
                    && ('0' <= c && c <= '9') == false) {
                return "アルファベットと数字でよろろ";
            }
        }

        return null;
    }

    public static UserId fromString(String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }
        return new UserId(value);
    }
}

validateメソッド内で詳しく値をチェックしてエラーがなければnullを、エラーがあったらエラーメッセージを返しています。

ConstraintValidatorのisValidメソッドではこのvalidateメソッドでエラーが返ってきたらそれをもとにConstraintViolationを組み立てます。

なぜこの方法を取るのか

私の大好きな JAX-RS ではリクエストパラメータやフォームパラメータを独自のクラスで受け取ることが出来ます。 で、jersey-mvc使って画面もモリモリ書いてるのでそれなりのメッセージが返るバリデーションをしたいのです。 しかもものぐさなので出来るだけ楽したいなー、と考えたり考えなかったりしながら色々試して今ここ、といった感じです。 それにしてもJAX-RSいいよJAX-RS。

ちなみにDomainTypeという名前にしているのはDDD由来ではなくて私の大好きな Doma というフレームワークの機能であるドメインクラスに対してバリデーションを付けることが多いのでそういう名前にしています。 いやホントDomaいいよDoma。

メリット&デメリット

この方法をとるとアノテーションは@DomainTypeを付けるだけで良いのでどのアノテーションを使えば良いのか迷うこともないしアノテーション地獄が少しマシになります。

デメリットもあって、これは自分でもイケてないと思いまくっているのですが、WithValidation実装クラスがバリデーションするために不正な状態を許している、という点です。 本来ならfromStringファクトリメソッドでバリデーションしておかしな値だったら例外投げるのが正道と思います。 まあメリットとデメリットを秤にかけて現状はこの方法を取っとくのがベターやな、といった所です。

おまけ:相関バリデーション

……というのかどうかは知りませんが「開始時刻」と「終了時刻」の前後関係が正しいか?みたいなふたつ以上の値を用いたバリデーションをする方法です。 簡単です。

BeanValidationはフィールドかgetterにアノテーションを付けてバリデーションを行うので一見相関バリデーションは行えない気がします。 が、例えば、ふたつの値をまとめるTupleというクラスを作ってそれに対してバリデーションするConstraintValidatorを作ればおkです。

試しにふたつの値が同じか検証するやつを書いてみました。

まずはTupleというクラスを導入。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

public class Tuple {

    public final String first;

    public final String second;

    public Tuple(String first, String second) {
        this.first = first;
        this.second = second;
    }
}

次にConstraintValidator。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

import java.util.Objects;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EqualValidator implements ConstraintValidator<Equal, Tuple> {

    @Override
    public void initialize(Equal constraintAnnotation) {
    }

    @Override
    public boolean isValid(Tuple value, ConstraintValidatorContext context) {

        if (value == null) {
            return true;
        }

        return Objects.equals(value.first, value.second);
    }
}

最後にアノテーション。

package net.hogedriven.backpaper0.javaeeadventcalendar2013;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { EqualValidator.class })
public @interface Equal {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        Equal[] value();
    }
}

特別なことはなにもないコードですね。

使い方は次のような感じです。

private String first;

private String second;

@Equal(message = "違う値はアカン")
public Tuple getValue() {
    return new Tuple(first, second);
}

また、相関バリデーションはひとつひとつの値がvalid前提であることが多いでしょうからgroupsを上手く使ってアレしてあげれば良いですね。

というわけで

自分なりのBeanValidationの使い方でした。

Java EE Advent Calendar 2013、明日のご担当は @kazuhira_r さんです。

]]>
Tue, 03 Dec 2013 00:00:00 +0000
https://backpaper0.github.io/2013/09/26/glassfish_server_xpoweredby.html https://backpaper0.github.io/2013/09/26/glassfish_server_xpoweredby.html <![CDATA[GlassFish v3.1.2.2でServerヘッダとX-Powered-Byヘッダを返さないようにする]]> GlassFish v3.1.2.2でServerヘッダとX-Powered-Byヘッダを返さないようにする

まずはServerヘッダ。

asadmin create-jvm-option -Dproduct.name=

JVMオプションなので設定の反映にはGlassFishを再起動する必要があります。

次、X-Powered-Byヘッダ。

asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.xpowered-by=false

こちらは設定は則反映されます。

で、JSP。 default-web.xmlでJspServletを設定しているところを見つけます。 でフォルトではxpoweredByがtrueに設定されているのでこれをfalseにします。

<servlet>
  <servlet-name>jsp</servlet-name>
  <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
  <init-param>
    <param-name>xpoweredBy</param-name>
    <param-value>false</param-value>
  </init-param>

最後にJSF。 web.xmlでFacesServletを設定する際にinit-paramでcom.sun.faces.sendPoweredByHeaderをfalseに設定すると良いようです。 試してない。

]]>
Thu, 26 Sep 2013 00:00:00 +0000
https://backpaper0.github.io/2013/07/17/jaxrs_parameter.html https://backpaper0.github.io/2013/07/17/jaxrs_parameter.html <![CDATA[JAX-RSでパラメータの受け取り方をいろいろ試す]]> JAX-RSでパラメータの受け取り方をいろいろ試す

JAX-RSでは次に挙げるアノテーションをメソッドの引数などに付けることでクエリパラメータやパスの一部を受け取ることができます。

  • @MatrixParam
  • @QueryParam
  • @PathParam
  • @CookieParam
  • @HeaderParam

メソッドの引数はStringやプリミティブを使うことができますが、それ以外のクラスも使用できます。

Stringの引数をひとつだけ受け取るpublicなコンストラクタを持つクラス

次のようなクラスでもクエリパラメータなどを受け取ることが出来ます。

public class Hoge {

    public final String value;

    public Hoge(String value) {
        this.value = value;
    }
}

リソースメソッドでは次のように使います。

@GET
public String get(@QueryParam("abc") Hoge abc) {
    ...

この例でいうとabcという名前のクエリパラメータがnullの場合はHogeはインスタンス化されず引数abcはnullとなります。 クエリパラメータがnullの場合というのはabcという名前のクエリパラメータが無い場合です。 ?abc=&def=xyz というようなクエリパラメータの場合はabcはnullではなく空文字列です。

Stringの引数をひとつだけ受け取る”valueOf”という名前のstaticファクトリメソッドを持つクラス

次のようなクラスを使用することも可能です。

public class Hoge {

    public final String value;

    private Hoge(String value) {
        this.value = value;
    }

    public static Hoge valueOf(String value) {
        return new Hoge(value);
    }
}

staticファクトリメソッドを書く分の手間が増えますが、例えば、

  • valueが空文字列の場合はnullを返す
  • Integerのようにキャッシュすることができる
  • サブクラスを返すことができる

という風に柔軟に実装することが可能です。 また、クラス(この例でいうとHoge)をabstractにすることも可能です。

Stringの引数をひとつだけ受け取る”fromString”という名前のstaticファクトリメソッドを持つクラス

staticファクトリメソッドはvalueOf以外にfromStringという名前にすることも可能です。

ひとつのクラスにvalueOfとfromStringの両方が定義されている場合はvalueOfが呼ばれます。 ただし列挙型の場合はvalueOfが暗黙的に実装されており挙動を上書きできないためか、fromStringが呼ばれます。 列挙型でなくともfromStringを優先にしておけば良かったのでは、と思わなくもないです。

ParamConverterを使用する

JAX-RS 2から

というふたつのインターフェースが追加されました。

ParamConverterの実装クラスではStringから任意のクラスに変換するロジックを書きます。

public class HogeParamConverter implements ParamConverter<Hoge> {

    @Override
    public Hoge fromString(String value) {
        return new Hoge(value);
    }

    @Override
    public String toString(Hoge hoge) {
        return hoge.value;
    }
}

ParamConverterProviderの実装クラスではリソースメソッドの引数の型やアノテーションをもとにParamConverterを選択して返します。

public class HogeParamConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        if (rawType == Hoge.class) {
            return (ParamConverter<T>) new HogeParamConverter();
        }
        return null;
    }
}

この方法は一番手間がかかりますがstaticファクトリメソッドを用いる方法と比べて

  • Hogeをインターフェースにすることが可能
  • 同じ型に対してもアノテーションによって異なるParamConverterを使用できる
  • javaから始まるパッケージのクラスなど既存のクラスも使用できる

といったことが利点だと思います。

まとめ

JAX-RS 2からParamConverterが入った事でクリエパラメータやリクエストヘッダをどんな型でも受け取ることができるようになりました。 インターセプターやクライアントAPIに比べると地味に見えますが、なかなか素晴らしい進化だと個人的には思っています。

サンプル書きました。

]]>
Wed, 17 Jul 2013 00:00:00 +0000
https://backpaper0.github.io/2013/07/15/websocket_pathparam.html https://backpaper0.github.io/2013/07/15/websocket_pathparam.html <![CDATA[Java API for WebSocketの@PathParamを試す]]> Java API for WebSocketの@PathParamを試す

@PathParam というアノテーションでパスの一部をメソッドの引数で受け取ることができるようなので試してみました。

package chat;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/chat/{guest-id}")
public class ChatEndPoint {

    private static CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(@PathParam("guest-id") String guestId, Session session) {
        sessions.add(session);
        for (Session s : sessions) {
            s.getAsyncRemote().sendText(guestId + "さんが入室しました");
        }
    }

    @OnMessage
    public void onMessage(@PathParam("guest-id") String guestId, String text) throws
            IOException {
        for (Session s : sessions) {
            s.getAsyncRemote().sendText("[" + guestId + "] " + text);
        }
    }

    @OnClose
    public void onClose(@PathParam("guest-id") String guestId, Session session) {
        sessions.remove(session);
        for (Session s : sessions) {
            s.getAsyncRemote().sendText(guestId + "さんが退室しました");
        }
    }
}

クラスに付けたServerEndpointに書かれたパスの一部が {guest-id} となっていますね。 それをメソッドの引数に @PathParam(“guest-id”) と付けて受け取っています。 簡単ですね。

今回のソース

今回作ったクラスはchatというパッケージにしています。 また、mvn -Pchat exec:java とすればサーバが立ち上がるようにしています。 クライアントはchat.htmlです。 Chromeで何となく動くことを確認しています。 サーバを終了したい場合はコンソールで何かキーを押してください。

なお、今回のコードはきしださんが下記エントリで書かれたものを参考にしました。

]]>
Mon, 15 Jul 2013 00:00:00 +0000
https://backpaper0.github.io/2013/07/14/websocket.html https://backpaper0.github.io/2013/07/14/websocket.html <![CDATA[Java SEでWebSocketサーバを立てて遊ぶ]]> Java SEでWebSocketサーバを立てて遊ぶ

先だってリリースされたJava EE 7に JSR 356: Java API for WebSocket が入りました。 GlassFish v4などを利用すればWebSocketで遊べます。

が、やっぱJava SEで動かしたいですよね? ね?

というわけでJSR 356の参照実装であるTyrusを使います。

サーバ側のエンドポイントを作成します。 POJOにアノテーションを付ける感じです。

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/echo")
public class EchoServerEndPoint {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("[open] " + session);
    }

    @OnMessage
    public String onMessage(String message, Session session) {
        System.out.println("[" + message + "] " + session);
        return message;
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("[close] " + session);
    }
}

@OnOpenはセッションが確立したとき、@OnMessageはクライアントからメッセージが届いたとき、@OnCloseはセッションが切断されたときに呼ばれます。 @OnMessageを付けたメソッドではクライアントが送信したテキストをStringの引数で受け取ることができます。 なお、バイナリメッセージだとbyte[]やByteBufferで受け取れるようです。 また、このメソッドはStringを返していますがこれはクライアントへ送信されるテキストメッセージとなります。

では次にこれをJava SEで動かすためのコードを書きます。

import java.io.IOException;
import javax.websocket.DeploymentException;
import org.glassfish.tyrus.server.Server;

public class EchoMain {

    public static void main(String[] args) throws Exception {
        Server server = new Server("localhost", 8080, "/ws", EchoServerEndPoint.class);
        try {
            server.start();
            System.in.read();
        } finally {
            server.stop();
        }
    }
}

Serverのコンストラクタにホスト、ポート、ルートパス、エンドポイントのクラスを渡してインスタンス化し、startメソッドを呼べばWebSocketサーバの出来上がりです。 あとはクライアントから ws://localhost:8080/ws/echo に接続すればOKです。 簡単ですね。

]]>
Sun, 14 Jul 2013 00:00:00 +0000
https://backpaper0.github.io/2013/07/07/devkan_jaxrs.html https://backpaper0.github.io/2013/07/07/devkan_jaxrs.html <![CDATA[DevLOVE関西「開発スターターキット」におけるJAX-RSの簡単な解説]]> DevLOVE関西「開発スターターキット」におけるJAX-RSの簡単な解説

どうも、GlassFishとJAX-RSを(・∀・)イイヨイイヨーと言っていた2番テーブルTAのうらがみです。

というわけで参加されたみなさん、お疲れ様でした。 いいよいいよと言いまくってた手前、今回のJAX-RSを利用していたソースコードをかるーく解説しておこうと思います。

まずはDevKanApplication.javaです。

@ApplicationPath("/services")
public class DevKanApplication extends Application {
}

Applicationを継承して@ApplicationPathで注釈していますが、このクラスがあればGlassFishさんが 「お、これはJAX-RSの出番やな( ー`дー´)キリッ」 という感じで認識してくれます。 なお@ApplicationPathに設定している /services が基点となるパスになります。

次いでCalculator.javaです。

@Path("/calc")
@Produces(MediaType.TEXT_PLAIN)
public class Calculator {

    @GET
    @Path("add")
    public String add(@QueryParam("a")int a, @QueryParam("b")int b){
        return "2";
    }
}

アノテーションがもりもり書かれていますが、これらはHTTPリクエスト/レスポンスに対応しています。 足し算のHTTPリクエスト/レスポンスはこんな感じになりますよー、っていうのを何となく書き出します。

リクエストはこんな感じ。

GET /devkan-calc/services/calc/add?a=2&b=3 HTTP/1.1

レスポンスはこんな感じ。

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 1

2

見比べるとよく分かりますが、

  • メソッドに書かれた@GETはリクエストメソッドに、
  • クラスとメソッドに書かれた@Pathはリクエストのパスに、
  • メソッドのパラメータに書かれた@QueryParamはリクエストのクエリパラメータに、
  • クラスに書かれた@ProducesはレスポンスのContent-Typeに、

それぞれ対応しています。 ついでに言うと、戻り値はレスポンスのエンティティボディに対応していますね。 HTTPを知っていればJAX-RSは使えるよー、という感じですね。

以上、JAX-RSの簡単な解説おわり。

WARを覗く

jar -tf build/libs/devkan-calc.war と打ってWARの中身を覗いてみましょう。 WARファイルの中身、WEB-INFディレクトリの下にあるのは DevKanApplication.class と Calculator.class だけです。 フレームワークのJARファイルはおろか、web.xmlすらありません。

JAX-RSはJava EEの一部であり、Java EEのアプリケーションサーバであるGlassFishにデプロイする場合はフレームワークのJARファイルは不要です。

また、最近ではサーブレットの登録にもweb.xmlは必須ではありません。 @WebServletというアノテーションで登録するか、ServletContextのaddServletメソッドで動的に登録できます。

この辺りはまた別の機会に詳しく紹介するとして(やらないフラグ)、大事なのは「JAX-RSなら2クラスでWebアプリ作れるですよ(`・ω・´)シャキーン」ということです。 お手軽ですね。

JAX-RSをもっと知りたい場合は

@btnrougeさん のブログ Programming Studio を読むと良いでしょう。 JAX-RSのタグが付けられたエントリをざくざく読んで行けばもりもり知識がつくと思います。

それと、手前味噌ですが、私もJAX-RSを一通り紹介するエントリを書きました。

ただし対象バージョンはちょっと古いです。 エントリは1.1。 最新は2.0。 そのうちエントリも2.0にアップデートしたいです(やらないフラグ)。

書籍なら「JavaによるRESTfulシステム構築」(ISBN:978-4873114675)が良いですかねー。 日本語訳が2010年に出版された書籍で、こちらも内容はJAX-RS 1系ですが今でも通用する情報が載っているとは思います。

そんな感じで、JAX-RSの回し者、うらがみでした(・∀・)

]]>
Sun, 07 Jul 2013 00:00:00 +0000
https://backpaper0.github.io/2013/06/02/jsr330.html https://backpaper0.github.io/2013/06/02/jsr330.html <![CDATA[JSR 330を実装してみた]]> JSR 330を実装してみた

JSR 330はDIの仕様です。 参照実装はGuiceです。

仕様は薄いしTCKもあるので実装してみました。

まあ何とかTCK通しただけですががが。

JSR 330の仕様では@Injectで注釈しているメソッドがインジェクション対象となりますが、サブクラスでオーバーライドされており、オーバーライドされたそのメソッドが@Injectで注釈されていなければインジェクション対象とはなりません。

で、メソッドがスーパークラスのメソッドをオーバーライドしているのかどうかはリフレクションで取得できるだろー、と思ってたけどできませんでした。

んで、スーパークラスに同じシグネチャのメソッドがあればオーバーライドしていると判断してええじゃろ、と思ってたけどそんな簡単なアレじゃありませんでした。 メソッド名と引数の数・型が同じでも例えば可視性がpackage privateでスーパークラスとサブクラスが異なるパッケージであればオーバーライドしていません。 パッケージが一緒でもスーパークラスの方で定義されたメソッドがprivateだとやっぱりオーバーライドしていません。

というわけでオーバーライドしてるかどうかの判定が面倒で泥臭くなっています。 もっと良い方法があれば教えてください、的な。

]]>
Sun, 02 Jun 2013 00:00:00 +0000
https://backpaper0.github.io/2013/05/02/jaxrs.html https://backpaper0.github.io/2013/05/02/jaxrs.html <![CDATA[JAX-RSとかの話]]> JAX-RSとかの話

これは2013-04-19に行われた「いいね!Java EE!」で使用した資料を加筆修正したものです。

JAX-RSって?

  • Webアプリを作るためのAPI
  • JSR 311 (JAX-RS 1.x)
  • JSR 339 (JAX-RS 2.x)
  • Java EE 6 Full Profile に入ってる(なぜかWeb Profileじゃない)
  • Java EE 7 からは Web Profile に入る
  • Jersey(参照実装)、RESTEasy、Apache CXFなどの実装があり、みんな大好きTomcatでも使える
  • 仕様書(PDF)は41ページで目に優しい(ちなみにEJB 3.1は626ページ)

開発準備

以上

Maven

Mavenでアレするなら次のようなdependencyを書けば良いです。

<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-bundle</artifactId>
  <version>1.11.1</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.sun.jersey.jersey-test-framework</groupId>
  <artifactId>jersey-test-framework-http</artifactId>
  <version>1.11.1</version>
  <scope>test</scope>
</dependency>

Jerseyのartifactはjersey-serverやjersey-jsonなどいくつかに分かれているのですが、jersey-bundleはそれらがまとめられたもので、こいつを指定するのが楽ちんです。

jersey-test-framework-http はJerseyのテスティングフレームワークで、Hotspotに入ってる com.sun.net.httpserver.HttpServer で実行します。

artifactId の末尾の -http を -grizzly にするとGrizzly(GlassFishのHTTPを捌く部分)で動かす事もできますし、-inmemory にするとソケットを介さずインメモリで動かす事もできます。

はじめてのJAX-RS

クエリパラメータで名前を渡すとHello, xxx!が返るWeb APIを書きましょう。

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

@Path("hello")
public class HelloResource {

    @GET
    public String say(
            @QueryParam("name") String name) {
        return "Hello, " + name + "!";
    }
}

これは次のようなHTTPリクエストを処理することができます。

GET /rest/hello?name=world HTTP/1.1

/rest がアプリケーションのルートとなります。 これは後述するApplicationサブクラスに注釈する@ApplicationPathで指定する値が対応します。 あとは何となく見たら分かる感じではありますが、/hello が @Path(“hello”) に、?name=world が @QueryParam(“name”) に、それからリクエストメソッドがGETですが @GET が対応しています。

こいつをとりあえずサクッと動かすならjersey-test-frameworkを使うのがらくちんです。 JUnitでぶん回すことができます。

import com.sun.jersey.test.framework.AppDescriptor;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.LowLevelAppDescriptor.Builder;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class HelloResourceTest extends JerseyTest {

    @Test
    public void test_say() throws Exception {
        String response = resource()
                .path("hello")
                .queryParam("name", "world")
                .get(String.class);
        assertThat(response, is("Hello, world!"));
    }

    @Override
    protected AppDescriptor configure() {
        return new Builder(HelloResource.class).build();
    }
}

アプリケーションサーバやサーブレットコンテナで動かす

GlassFish などのJava EEアプリケーションサーバで動かすにはApplicationサブクラスを作ります。

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("rest")
public class JaxrsActivator extends Application {
}

Applicationを継承して@ApplicationPathで注釈するだけです。 あとはHelloResourceとこのJaxrsActivatorをWARにパッケージングしてデプロイすればおkです。

Tomcatなどのサーブレットコンテナだと専用のサーブレットを登録する必要がある……と見せかけてServlet 3対応のコンテナならJerseyのJARを突っ込むだけでweb.xmlを書く必要はありません。 web.xmlを書かなくてもServletContainerInitializerを利用して動的にサーブレットを追加してくれます。

リソースクラス、リソースメソッド

リソースクラスは@Pathで注釈したクラスで先の例でいうとHelloResourceがリソースクラスになります。 クラス名に制約はないのでHelloでもFoobarでも何でも良いです。 @Pathにはこのリソースクラスで処理するパスを指定します。 リソースクラスはpublicなコンストラクタが必要です。

@Path("hello")
public class HelloResource { ... }

リソースメソッドはリソースクラスに定義されたメソッドで@GETや@POSTといったHTTPメソッドに対応するアノテーションで注釈します。 リソースメソッドでは@Consumesで受け取るリクエストボディのContent-Typeを指定できます。 同じように@Producesで送り返すレスポンスボディのContent-Typeを指定できます。 また引数に@QueryParamや@HeaderParamを注釈することでクエリパラメータやリクエストヘッダをマッピングすることができます。

@GET
@Consumes("text/plain")
@Produces("text/plain")
public String say(
        @QueryParam("name") @DefaultValue("world") String name) {
    return "Hello, " + name + "!";
}

なお、@QueryParamなどでマッピング出来るのはリソースメソッドの引数だけじゃなくコンストラクタの引数や

@Path("hello")
public class HelloResource {

    private final String name;

    public HelloResource(
            @QueryParam("name") String name) {
        this.name = name;
    }

    ...

フィールド、

@Path("hello")
public class HelloResource {

    @QueryParam("name")
    private String name;

    ...

setterなども使用できます。

@Path("hello")
public class HelloResource {

    private String name;

    @QueryParam("name")
    public void setName(String name) {
        this.name = name;
    }

    ...

個人的にはメソッドの引数を使用するのが好きです。

パラメータのマッピング

既にクエリパラメータを@QueryParamでマッピングする例は挙げましたが、他にはリクエストヘッダやパスの一部、Cookieの値などをアノテーションでマッピングすることができます。

@QueryParam

  • クエリパラメータをマッピング
  • /hoge?name=value
@GET
public String sayHello(
        @QueryParam("name") String name) {
    ...

@FormParam

  • フォームのPOSTリクエストで送信するパラメータをマッピング
  • <input type=”text” name=”name”>
@POST
public String sayHello(
        @FormParam("name") String name) {
    ...

@PathParam

  • パスの一部をマッピング
  • /hoge/value
@GET
@Path("{name}")
public String sayHello(
        @PathParam("name") String name) {
    ...

コロンで区切って正規表現を書く事もできます。

@GET
@Path("{id:[0-9]{1,10}}")
public String findById(
        @PathParam("id") Long id) {
    ...

@MatrixParam

  • マトリックスパラメータ
  • セミコロンで区切った形式
  • /hoge;foo=1;bar=2
@GET
@Produces("text/plain")
public String sayHello(
        @MatrixParam("left") String left,
        @MatrixParam("right") String right) {
    ...

@CookieParam

  • Cookieをマッピング
@GET
@Produces("text/plain")
public String sayHello(
        @Cookie("name") String name) {
    ...

@HeaderParam

  • リクエストヘッダをマッピング
@GET
@Produces("text/plain")
public String sayHello(
        @HeaderParam("name") String name) {
    ...

パラメータをまとめる

パラメータがひとつふたつなら良いですが、もっと多くなると引数に列挙するのはシグネチャがうるさくなりますね。 Jerseyなら@InjectParamを使うことでパラメータをPOJOにまとめることができます。 ただし、JAX-RSの仕様じゃなくてJerseyの実装依存の機能ですので、そこんとこ注意です。

@GET
@Produces("text/plain")
public String sayHello(
        @InjectParam HogeBean bean) {
    ...

public class HogeBean {

    @QueryParam("foo")
    public String foo;

    @QueryParam("bar")
    public String bar;

    ...

ちなみにJAX-RS 2からはこの@InjectParamと同様の機能をもつ@BeanParamというアノテーションが追加されます。

パラメータをPOJOで受け取る

ここまで@QueryParamなどで受け取るパラメータの型はStringを使用していましたが、自作のクラスを使用することも可能です。 パラメータを受け取れるクラスは、valueOfまたはfromStringという名前の静的ファクトリメソッドを定義する必要があります。 引数はStringです。

public class Fullname {

   ...

    //public static Fullname valueOf(String value) {
    public static Fullname fromString(String value) {
        return new Fullname(value);
    }
}

リソースメソッドではStringでパラメータを受けるときと同じ感覚で使えます。

@GET
@Produces("text/plain")
public String sayHello(
        @QueryParam("name") Fullname name) {
    ...

XMLで通信する

エンティティボディがXMLの場合、JAXBで自作のクラスにマッピングする機能がJAX-RSにはあります。

例えばこのようなXMLを、

<hogeBean>
  <foo>hello</foo>
  <bar>world</bar>
</hogeBean>

このようなクラスで受け取ることが可能です。 @XmlRootElementはJAXBのAPIです。

@XmlRootElement
public class HogeBean {

    public String foo;

    public String bar;
}

リソースメソッドはこのようになります。 @ConsumesでXMLを受け取る事を明示しています。

@POST
@Consumes("application/xml")
public void doHoge(HogeBean bean) {
    ...

レスポンスをXMLにすることも可能です。 その場合、リソースメソッドは次のようになります。 今度は@ProducesでXMLを返すことを明示しています。

@GET
@Produces("application/xml")
public HogeBean getHoge() {
    ...

前述の通りXMLとクラスの相互変換を行う部分はJAX-RSではなくJAXBの仕様です。 JAXBはJava SEに入っているので動作確認は手軽にできます。

HogeBean obj = ...
StringWriter out = new StringWriter();
JAXB.marshal(obj, out);
String xml = out.toString();

...

String xml = ...
StringReader in = new StringReader(xml);
HogeBean obj = JAXB.unmarshal(xml, HogeBean.class);

JSONで通信する

前述のようにJAXBでXML通信している場合、Jerseyなら@Consumesや@Producesでのメディアタイプの指定をapplication/jsonに変更するだけでJSONで通信することが可能です。

@POST
//@Consumes("application/xml")
//@Produces("application/xml")
@Consumes("application/json")
@Produces("application/json")
public HogeBean doHoge(HogeBean bean) {
    ...

元々はXMLで通信していましたが、たったこれだけで次のようなJSONで通信するようになります。

{ "foo" : "hello",
  "bar" : "world" }

JSON通信での問題点

リストを含む次のようなクラスの、

@XmlRootElement
public class Hoge {

    public List<String> foobar;
}

インスタンスを作成して、

Hoge obj = new Hoge();
obj.foobar = Arrays.asList("a", "b", "c");

XML通信すると次のようなXMLになります。

<hoge>
  <foobar>a</foobar>
  <foobar>b</foobar>
  <foobar>c</foobar>
</hoge>

foobar要素がリストの要素分、フラットに並んでいますね。

これがJSON通信の場合だと次のようなJSONになります。

{ "foobar" : [ "a", "b", "c" ] }

XMLではフラットに並んでいたfoobar要素が空気を読んでリストになっていますね。

で、次はこんな感じのインスタンスを、

Hoge obj = new Hoge();
obj.foobar = Arrays.asList("x");

JSON通信すると次のようなJSONになります。

{ "foobar" : "x" }

リストじゃなくなっていますね。 なんでやねん、と。

foobar要素が一つのXMLを想像するとなんとなく納得できます。

<hoge>
  <foobar>x</foobar>
</hoge>

このように、要素がひとつだとリストなのかリストじゃないのか分からないのです。 JerseyのJAXB経由のJSON変換は、XMLに変換する過程に横入りして行っているのでこの影響をモロに受けてリストじゃなくなってしまうっぽいです。

クライアント側もJerseyを使っていたりするとこれが問題になることは無いですが、WebアプリでjQueryなんかを使ってたりするとリストのつもりで受け取ったらリストじゃなかったでござる、という状況になって困ります。 というか困りました、実際に。

JacksonでJSON通信する

前述の「要素がひとつの場合にリストじゃなくなっちゃう問題」に対処するにはJacksonを利用したJSON変換を行うと良いです。

Jacksonを使うとfoobar要素がひとつしかない場合でも次のようなJSONに変換されます。

{ "foobar" : [ "x" ] }

また、Jacksonを使うと、

  • クラスを@XmlRootElementで注釈する必要がない
  • リソースメソッドの戻り値をjava.util.Listにする事が可能

などの利点があります。

JerseyでJacksonを使うには初期パラメータ com.sun.jersey.api.json.POJOMappingFeature を true に設定します。

jersey-test-frameworkを使ったりJDKのHttpServerで動かす場合はResourceConfigというクラスで設定すると良いです。

ResourceConfig rc = ...
rc.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);

書くまでもないですが、このコードにあるJSONConfiguration.FEATURE_POJO_MAPPINGは文字列の定数で、”com.sun.jersey.json.POJOMappingFeature”です。

サーブレット経由で動かすならweb.xmlで設定することも可能です。

<servlet>
  <servlet-name>Jersey</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
  </init-param>
</servlet>

個人的にはJerseyでJSON通信するならJacksonを使うのが良いと思います。

XMLでもJSONでも通信する

これまでXMLかJSONのどちらか片方で通信する設定方法を紹介しましたが、ひとつのメソッドでXMLでもJSONでも通信することも可能です。 設定は単純で@Consumesや@Producesに複数のメディアタイプを書けば良いです。

@POST
@Consumes({ "application/json", "application/xml" })
@Produces({ "application/json", "application/xml" })
public HogeBean doHoge(HogeBean bean) {
    ...

このリソースメソッドはクライアントがAcceptヘッダでapplication/jsonを要求すればJSONで通信し、application/xmlを要求すればXMLで通信します。 このようにメソッドの内容はまったく同じだけど、HTTPリクエストの内容によって通信のフォーマットを切り替えられるのはJAX-RSの強みですね。

MessageBodyReader/MessageBodyWriter

JAX-RSで通信できるのはXMLやJSONだけではありません。 MessageBodyReaderやMessageBodyWriterを実装すればエンティティボディを好きにマッピングすることが可能です。

例えば、String[][]をCSVで出力するMessageBodyWriterを実装してみます。

@Provider
@Produces("text/csv")
public class CsvWriter implements MessageBodyWriter<String[][]> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return type == String[][].class;
    }

    @Override
    public long getSize(String[][] t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(String[][] t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream) throws IOException, WebApplicationException {
        try (PrintWriter out = new PrintWriter(entityStream)) {
            for (String[] row : t) {
                for (String column : row) {
                    out.printf("%s,", column);
                }
                out.println();
            }
        }
    }
}

@Providerで注釈していますが、これを付けておくとアノテーションスキャンで拾ってくれます。 あるいはクラスパス上にMETA-INF/services/javax.ws.rs.ext.MessageBodyWriterというファイルを作って、中にCsvWriterのFQDNを書いてServiceLoaderに拾ってもらいます。

@Producesで注釈することで、このMessageBodyWriterが処理する対象となるContent-Typeを指定しています。 さらにisWritableメソッドでこのMessageBodyWriterを使うべきか判断することが可能です。

getSizeメソッドは書き出すエンティティボディのバイトサイズです。 算出できない(し辛い)場合は-1を返しておくと良きに計らってくれます。

最後にwriteToメソッドですが、これが実際にエンティティボディに書き出すメソッドになります。

このCsvWriterに対応するレスポンスを返すリソースメソッドはこんな感じです。

@GET
@Produces("text/csv")
public String[][] getCsv() {
    ...

オブジェクトから実際の通信形式へ変換する方法をMessageBodyWriterに分離しているのでリソースメソッドはシンプルに保たれていますね。

WebApplicationException

場合によってはリソースメソッドで処理中に「やっぱり404返したいわー」などというときもあると思いますが、WebApplicationExceptionを投げるのが楽ちんです。

@GET
@Path("{isbn}")
@Produces("application/json")
public Book get(@PathParam("isbn") Isbn isbn) {
    Book book = bookBean.get(isbn);
    if(book == null) {
        throw new WebApplicationException(404);
    }
    ...

ExceptionMapper

リソースメソッドから投げられた特定の例外を受け取って処理したい場合はExceptionMapperを実装したサブクラスを作ります。

例えばJPAの楽観排他機能で更新したいエンティティが既に別のひとに更新されていた場合、OptimisticLockExceptionが投げられますが、これを受け取って処理をするExceptionMapperを書いてみます。

@Provider
public class OptimisticLockExceptionMapper implements ExceptionMapper<OptimisticLockException> {

    @Override
    public Response toResponse(OptimisticLockException exception) {
        return Response.status(400).entity("更新されとった><").type("text/plain").build();
    }
}

@Contextで色々な情報を参照する

前述のOptimisticLockExceptionMapperですが、EJBを使っている場合はOptimisticLockExceptionがRollbackExceptionに包まれて投げられます。 RollbackExceptionはOptimisticLockExceptionと継承関係は無いのでOptimisticLockExceptionMapperで処理できません。

そんな場合はProvidersを使います。

public static class RollbackExceptionMapper implements ExceptionMapper<RollbackException> {

    @Context
    private Providers p;

    @Override
    public Response toResponse(RollbackException exception) {
        Throwable cause = exception.getCause();
        Class<Throwable> type = (Class<Throwable>) cause.getClass();
        return p.getExceptionMapper(type).toResponse(cause);
    }
}

@ContextでProvidersをインジェクションします。 Providersはクラスやアノテーションを渡すとそれに対応するExceptionMapperやMessageBodyReaderを取ってこれる便利なものです。 このような便利クラスを@Contextでインジェクションできます。

インジェクションできる便利クラスはProvidersの他にクエリパラメータやパスの情報を取って来れるUriInfoや、HTTPヘッダを取れるHttpHeaders、認証情報を取れるSecurityContextなどがあります。

他にもDIしたい

EJBでDI

リソースクラスをStateless Session Beanにします。 @EJBでSession Beanを、@PersistenceContextでEntityManagerなどをインジェクションできます。

@Path("hello")
@Stateless
public class HelloResource {

    @EJB
    private HelloBean helloBean;

    @GET
    public String say(@QueryParam("name") String name) {
        return helloBean.say(name);
    }
}

個人的な利点は宣言的トランザクションでしょうか。

欠点はSession BeanかEntityManagerぐらいしかDIするものがないことですかね。

CDIでDI

WEB-INF/beans.xmlを作成します。 空のファイルでもOKです。

@Path("hello")
@RequestScoped
public class HelloResource {

    @Inject
    private HelloBean helloBean;

    @GET
    public String say(@QueryParam("name") String name) {
        return helloBean.say(name);
    }
}

基本的に何でもDIできるのが利点です。 CDIでは殆どのクラスが管理Beanになります。

欠点はEJBでは使えていた宣言的トランザクション使えないことです。 まあ自分でCDIのインターセプターを書いて適用すれば良いんですが、ちょっと面倒です。

EJBとCDIを併用する

というわけでEJBとCDIを併用します。

@Stateless
@Path("hello")
public class HelloResource {

    @Inject
    private HelloBean helloBean;

    @GET
    public String say(@QueryParam("name") String name) {
        return helloBean.say(name);
    }
}

利点はDIを@Injectで統一できることです。 CDI管理Beanは当然ですが、(少し手を加える必要がありますが)EntityManagerもDataSourceも@Injectでぶっ込むことが可能です。

欠点はJersey 1.11.1のバグです。

その他、DIの利点

  • インターセプタをかませる(JAX-RS 2.0からはJAX-RSの仕様にインターセプターが入りますが)
  • モックにすげ替えやすい
  • Arquillianでテストしやすい

Arquillian

ArquillianはJBossが提供しているJava EE向けの結合テストフレームワークです。 以下の例のようにテストコードを書く事が可能です。

@RunWith(Arquillian.class)
public class CalcTest {

    @Inject
    CalcBean calcBean;

    @Test
    public void test_add() throws Exception {
        int answer = calcBean.add(2, 3);
        assertThat(answer, is(5));
    }

    @Deployment
    public static WebArchive createDeployment() {
        return ShrinkWrap.create(WebArchive.class)
            .addClass(CalcBean.class)
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}

JAX-RS 2.0の新機能

JAX-RS 2.0からは以下のような機能が追加されます。

  • フィルター
  • インターセプター
  • 非同期処理
  • クライアントAPI
  • Bean Validationとの統合

フィルター

リクエスト・レスポンスそれぞれに対応するフィルターを書けます。

@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        Logger.getLogger("request");
    }

    @Override
    public void filter(ContainerRequestContext requestContext,
            ContainerResponseContext responseContext) throws IOException {
        Logger.getLogger("response");
    }
}

インターセプター

エンティティボディを読み書きするところに横入りしてごにょごにょできます。

@Provider
public class StarInterceptor implements ReaderInterceptor, WriterInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException,
        WebApplicationException {
        Object entity = context.proceed();
        return "+" + entity + "+";
    }

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException,
        WebApplicationException {
        Object entity = context.getEntity();
        context.setEntity("*" + entity + "*");
        context.proceed();
    }
}

アノテーションで適用する範囲を決める

CDIも似たような感じですが、フィルター(インターセプター)にアノテーションを付けておくと同じアノテーションが付いているリソースクラス・リソースメソッドにそのフィルター(インターセプター)を噛ませることが出来るようです。

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,
         ElementType.METHOD})
public static @interface Logged {
}

@Logged
@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
    ...

リソースメソッドはこんな感じ。

@POST
@Logged
public String post(String s) {
    ...

非同期処理

Servlet 3.xにも非同期処理が入りましたが、JAX-RSにもやってきました。 以下のサンプルはJSR 339に乗っていたものです。

private static final BlockingQueue<AsyncResponse> suspended =
    new ArrayBlockingQueue<AsyncResponse>(5);

@GET
@Produces("text/plain")
public void readMessage(@Suspended AsyncResponse ar) throws InterruptedException {
    suspended.put(ar);
}

@POST
@Produces("text/plain")
public String postMessage(final String message) throws InterruptedException {
    final AsyncResponse ar = suspended.take();
    ar.resume(message); // resumes the processing of one GET request
    return "Message sent";
}

うん、使いどころがわかりません!

使ってみたい

というわけで、JAX-RS 2.0のリリースはまだですが、いち早く試したい場合はJersey 2.xを使ってみましょう!

<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
    <version>2.0-rc1</version>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-jdk-http</artifactId>
    <version>2.0-rc1</version>
    <scope>test</scope>
  </dependency>
</dependencies>

まとめ

  • JAX-RSいいね!
  • もうServletは要らないですね
  • ところで「いいね!Java EE!」の第二回はあるんですかね?

資料の手直し、最後の方疲れたので投げやりです。 まあ、またJAX-RSは話題に出すと思いますので書ききれなかった(書き忘れた)アレとかソレはまたの機会にー。

]]>
Thu, 02 May 2013 00:00:00 +0000
https://backpaper0.github.io/2013/04/01/shigatsubaka.html https://backpaper0.github.io/2013/04/01/shigatsubaka.html <![CDATA[退職しました]]> 退職しました

本日4/1をもって株式会社HOGEDRIVENを退職しました。

HOGEDRIVENでは並行プログラミング(焼肉)、ORマッパー(フグ)、暗号(焼肉)、Java 8のラムダ(ステーキ)など、様々な技術を経験させて貰いすごく感謝しています(ごちそうさまでした)。

しかし、HOGEDRIVENは「しゃぶしゃぶ」「ローストチキン」「シシケバブ」など、これからも肉料理に力を入れて行くようで、魚料理にも挑戦したいという私の思いとは目指す方向が異なるため、退職という道を選択しました。

今後の事はまだ何も決まっていませんが、ベタなところではマグロ、カニ、あるいは捨てる所が無いと言われるアンコウあたりに興味があるので、機会を見つけてチャレンジして行きたいと思います。

今後とも宜しくお願いただきます。

]]>
Mon, 01 Apr 2013 00:00:00 +0000
https://backpaper0.github.io/2013/03/03/like_bdd_by_java8.html https://backpaper0.github.io/2013/03/03/like_bdd_by_java8.html <![CDATA[Java8でBDDっぽくテストを書けるかもしれないアイデア]]> Java8でBDDっぽくテストを書けるかもしれないアイデア

JasmineでJavaScriptのテスト書いたりJava8のラムダに思いを馳せていたらなんとなく思いつきました。 ラムダにインスタンスイニシャライザを組み合わせたらこんな感じでテストが書けそうです。

package app;

public class CalcSpec extends Specs {{

    it("1 足す 2 は 3", () -> {
        expect(() -> 1 + 2).toEqual(3);
    });

    it("1 割る 0 は例外", () -> {
        expect(() -> 1 / 0).toThrow(ArithmeticException.class);
    });

}}

うむ、Jasmineぽい。 まあ実はBDDぽいとかよく分かっていませんが。

itメソッドを呼ぶと第一引数のテスト名をkey、第二引数のRunnableのようなオブジェクトをvalueとするマップに突っ込みます。 このマップのひとつのEntryがひとつのテストになります。 itメソッドはスーパークラスのSpecsに定義されています。 そのSpecs自体に@RunWithが注釈しており、テストはSpecRunnerというテストランナーで実行されます。 SpecRunnerはSpecsに集められたテストを実行します。

などと文章にしても伝えられる気がまったく無いのでSpecsとSpecRunnerのコード晒します。

Specsはこんな感じ。

package app;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.junit.Assert;
import org.junit.runner.RunWith;

@RunWith(SpecRunner.class)
public class Specs {

    public Map<String, Spec> specs = new LinkedHashMap<>();

    protected void it(String name, Spec spec) {
        specs.put(name, spec);
    }

    protected <T> Expect<T> expect(Callable<T> c) {
        return new Expect<>(c);
    }

    public interface Spec {

        void run() throws Exception;
    }

    public static class Expect<T> {

        private final Callable<T> c;

        public Expect(Callable<T> c) {
            this.c = c;
        }

        public void toEqual(T value) throws Exception {
            Assert.assertEquals(value, c.call());
        }

        public void toThrow(Class<? extends Exception> exceptionClass) throws Exception {
            try {
                c.call();
                Assert.fail();
            } catch (Exception e) {
                if (!exceptionClass.isAssignableFrom(e.getClass())) {
                    Assert.fail();
                }
            }
        }
    }
}

SpecRunnerはこんな感じ。

package app;

import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;

public class SpecRunner extends Runner {

    private final Specs spec;

    public SpecRunner(Class<Specs> specClass) throws InstantiationException, IllegalAccessException {
        spec = specClass.newInstance();
    }

    @Override
    public Description getDescription() {
        Description desc = Description.createSuiteDescription(spec.getClass());
        for (String name : spec.specs.keySet()) {
            desc.addChild(Description.createSuiteDescription(name));
        }
        return desc;
    }

    @Override
    public void run(RunNotifier notifier) {
        for (String name : spec.specs.keySet()) {
            Description desc = Description.createSuiteDescription(name);
            notifier.fireTestStarted(desc);
            try {
                spec.specs.get(name).run();
            } catch (Exception ex) {
                Failure f = new Failure(desc, ex);
                notifier.fireTestFailure(f);
            } finally {
                notifier.fireTestFinished(desc);
            }
        }
    }
}

これらのコードを、Java8に対応しているNetBeansの開発版を使用して試してみました。

../../../_images/test-result.png

いい感じで実行できました。

というわけで、ふわっとした思いつきをサクッと試してみただけですが、なかなかJava8の可能性を感じれた気がします。 テスティングフレームワークに限らずね、色々出てきてくれそうですね。

楽しみだ。はよこいJava8。

ギットハブにも置いたよ

]]>
Sun, 03 Mar 2013 00:00:00 +0000
https://backpaper0.github.io/2013/01/30/httpserver_jaxb.html https://backpaper0.github.io/2013/01/30/httpserver_jaxb.html <![CDATA[HttpServerとJAXBでAPIを組込む]]> HttpServerとJAXBでAPIを組込む

前に小酒さんとJavaのプロセス間通信についてお話しました。

んで、私も監視というかAPIというか、そんな感じのアレを簡易で良いのでサクッと組込む必要が出てきました。 という訳で HttpServer とJAXBでサクッと組込もうと思います。

ほい、サンプルコード。

import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlRootElement;

public class Monitor {

    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(8080);
        int backlog = -1;
        HttpServer server = HttpServer.create();
        server.bind(address, backlog);
        try {
            HttpContext context = server.createContext("/log");
            HttpHandler handler = new LogHandler();
            context.setHandler(handler);
            server.start();

            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    String message = "ダイアログを閉じると鯖終了";
                    JOptionPane.showMessageDialog(null, message);
                }
            });
        } finally {
            server.stop(10);
        }
    }

    public static class LogHandler implements HttpHandler {

        @Override
        public void handle(HttpExchange exchange) throws IOException {
            Log log = new Log();
            log.result = Result.SUCCESS;
            log.timestamp = new Date();

            int statusCode = 200;
            int chunked = 0;
            exchange.sendResponseHeaders(statusCode, chunked);

            try (OutputStream out = exchange.getResponseBody()) {
                JAXB.marshal(log, out);
                out.flush();
            }
        }
    }

    @XmlRootElement
    public static class Log {

        public Result result;

        public Date timestamp;

    }

    public enum Result {

        SUCCESS, FAILURE

    }
}

HttpServerはアドレス渡して準備して、パス渡してコンテキスト作って、ハンドラ登録すればおk。 Java SEでもHTTP鯖立てるの簡単ですね。 わーい。

今回はアプリケーションの処理結果とタイムスタンプを持ったログを返す感じのハンドラを書いてみました。 ログはPOJOで表現していますが、JAXBでXMLに変換してレスポンスに書き出しています。 このハンドラは次のようなXMLを返します。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<log>
    <result>SUCCESS</result>
    <timestamp>2013-01-30T23:08:37.482+09:00</timestamp>
</log>

XMLベースのWeb APIなら構築するのは簡単ですね。 わーい。

まあもうちょい多機能なAPIを構築したかったらJerseyを突っ込みますが要件によってはこれで十分かなー、とか思ったりしました。

]]>
Wed, 30 Jan 2013 00:00:00 +0000
https://backpaper0.github.io/2013/01/23/netbeans_jpql.html https://backpaper0.github.io/2013/01/23/netbeans_jpql.html <![CDATA[NetBeans 7.2.1でJPQLの補完するー]]> NetBeans 7.2.1でJPQLの補完するー

こんばんは! em.cQ と打って control + space で createQuery を補完するうらがみです。

もうキャメルケースでの補完が無かったら生きて行けません。 これはEcliなんとかでも出来ます。 たぶんIDEAでも出来るでしょう。

本題。 NetBeansではJPQLの補完もできます、という話。

../../../_images/compl1.png

FROM も。

../../../_images/compl2.png

エンティティ名も。

../../../_images/compl3.png

WHERE や、

../../../_images/compl4.png

プロパティも。

../../../_images/compl5.png

演算子も。

../../../_images/compl6.png

という感じです。 でもこれローカル変数に突っ込むと補完してくれないのが残念です。

../../../_images/compl7.png

あと@NamedQueryに書くJPQLでも補完できます。

../../../_images/compl8.png

ローカル変数に突っ込んだときも補完が効いてくれるようになるともっと嬉しいですねー。 簡単ですが、そんな感じでー。

]]>
Wed, 23 Jan 2013 00:00:00 +0000
https://backpaper0.github.io/2013/01/12/http_trace.html https://backpaper0.github.io/2013/01/12/http_trace.html <![CDATA[GlassFish v3.1.2.2でTRACEメソッドを許可しない]]> GlassFish v3.1.2.2でTRACEメソッドを許可しない

デフォルトで許可しないようになっていますが備忘録的に一応書いておきます。 asadminコマンドで設定します。

asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.trace-enabled=false

もちろんGUI管理コンソールからも設定可能で、構成 -> server-config -> ネットワーク構成 -> プロトコル -> http-listener-1 の「HTTP」タブを開いてトレースのチェックを外します。

この状態でTRACEリクエストを投げると405 Method Not Allowedが返ってきます。

]]>
Sat, 12 Jan 2013 00:00:00 +0000
https://backpaper0.github.io/2013/01/11/cookie.html https://backpaper0.github.io/2013/01/11/cookie.html <![CDATA[GlassFish 3.1.2.2でCookieにsecure属性とhttpOnly属性をつける]]> GlassFish 3.1.2.2でCookieにsecure属性とhttpOnly属性をつける

何もしなくてもhttpOnly属性は付いてる感じですが、それはGrizzlyの機能なのでしょうかね。 それはそれでまた調べておくことにします。

プログラムで設定する

javax.servlet.SessionCookieConfig を使用します。

package example;

import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig;

public class CookieSettings implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        SessionCookieConfig scc = ctx.getSessionCookieConfig();
        scc.setHttpOnly(true);
        scc.setSecure(true);
    }
}

GlassFish Server Deployment Descriptorで設定する

cookie-properties - Oracle GlassFish Server 3.1 Application Deployment Guide を参照。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC
  "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN"
  "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
 <session-config>
  <cookie-properties>
   <property name="cookieSecure" value="true"/>
   <property name="cookieHttpOnly" value="true"/>
  </cookie-properties>
 </session-config>
</glassfish-web-app>
]]>
Fri, 11 Jan 2013 00:00:00 +0000
https://backpaper0.github.io/2013/01/06/welcome_to_tinkerer.html https://backpaper0.github.io/2013/01/06/welcome_to_tinkerer.html <![CDATA[Welcome to Tinkerer]]> Welcome to Tinkerer

あけおめ!

そんなアレではてなダイアリーから乗り換える何かを模索中なのです。 TinkererというSphinxでブログが書ける拡張を見つけたので何となく使ってみようかと。 このエントリではインストールからbackpaper0.github.comでの公開までの手順を適当に書きます。 なお、エントリのタイトルはウェルカム・トゥ・オクトプレスをパクったです。

Tinkererをインストールする

easy_installで一発ですよ奥さん。

easy_install -U Tinkerer

セットアップする

適当にディレクトリ作ってからtinker -sします。

mkdir blog
cd blog
tinker -s

conf.pyが作成されるのでブログの名前とか設定します。

記事を書く

エントリ作ります。

tinker -p "Welcome to Tinkerer"

日付を表すディレクトリに続きエントリのrstファイルが作成されますのでこれを編集します。

ビルドする

tinker -b

blog/htmlディレクトリにもろもろ出力されます。

公開する

blog/html以下をbackpaper0.github.comにプッシュします。 まあぶっちゃけこれからやることなのでこの記事書いてる段階ではそれで良いのか分かりませんが。

まとめ

  • rstで書けるから嬉しい
  • Sphinx好きなので嬉しい
  • なんか探すときはgrep出来るから嬉しい
  • Tinkerer簡単そうで嬉しい

という感じでしばらく使ってみようと思います。

]]>
Sun, 06 Jan 2013 00:00:00 +0000