読者です 読者をやめる 読者になる 読者になる

その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Server::Starter + Java 環境下で JMX による監視を有効にしていると graceful restart 時に不具合が出ちゃって困るんですけど〜って時

java

割とニッチな話題ではありますが……


Server::Starter を使ってプロセスを立ち上げると graceful restart を簡単に実現できるなど便利な点が多く,LL 時代はこれでやっていっていたわけですが,残念なことに Java からその Server::Starter テクノロジを利用するのは長らく不可能なものと思われてきました.しかし昨年2015年の中頃に Java からでも Server::Starter の利用が可能であることが id:tokuhirom 氏により発見された (+ Server::Starter にパッチが送られた) ため,Java からでもお手軽に Server::Starter を用いた graceful restart が出来るようになりました.
Server::Starter については参考になる記事がインターネット上にたくさんありますから適宜検索してもらうとして,Java から Server::Starter を利用する術については以下を参照してください.


さて本題ですが,タイトルが長いのでわかりやすく分割して書きますと,

  • Server::Starter を使って Java プロセスを立ち上げていて
  • JMX による 監視を有効にしていて
  • Graceful Restart を行った時

に,JMX が port を食い合ってしまうために上手く restart できないという問題についての話です.

例えば,以下のようにして Server::Starter + Java + jmx を起動してから,

$ java \
    ...
    -Dcom.sun.management.jmxremote \
    -Dcom.sun.management.jmxremote.port=5555 \
    -Dcom.sun.management.jmxremote.rmi.port=5555 \
    Main

restart を行うと,瞬間的に2つの Java のプロセスが立ち上がるため,JMX の connector server が port を食い合って (新プロセスの方が "Address already in use" を吐く) 新しいプロセスを上手く立ち上げることがきなくなります.こうした場合,connector server は立ち上がりませんし,手法によっては古いプロセス・新しいプロセスの両方とも終了せずに無限に起動し続けるといった,言うなればデッドロックのような状況に陥るかもしれません.

というわけでどうするかというと,コマンドラインオプションを指定する代わりに手で connector server を立ち上げるコードを書いて,daemon の起動時に立ち上げてやります.

public void startJMXConnectorServer() throws IOException {
    final int port = 5555;
    LocateRegistry.createRegistry(port);
    final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
    final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
    JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer).start();
}

こんな感じのものを書いて,立ち上げのタイミングで startJMXConnectorServer() を呼び出してやると,コマンドラインオプションで指定した時と同様の効果が得られます.
が,しかしこれで上手くいくかと思いきや,このままだとコマンドラインオプションを使った手法と同様で上手く resstart することが出来ません (依然 "Address already in use" が出る).
ので,苦肉の策でこうしてやる.

public void startJMXConnectorServer() {
    final int port = 5555;
    boolean isJMXLaunched = false;
    for (int i = 0; i < 100; i++) {
        try {
            LocateRegistry.createRegistry(port);
            final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer).start();
        } catch (IOException e) {
            // Might be "address already in use" error, retry
            try {
                Thread.sleep(300);
            } catch (InterruptedException ie) {
                log.warn("Interrupted");
            }
            continue;
        }
        isJMXLaunched = true;
        break;
    }
    if (!isJMXLaunched) {
        throw new RuntimeException("Failed to start JMX connector server");
    }
    log.info("JMX connector server started");
}

スピンロックのような感じで port が空くまで待って,port が空いた,すなわち古いプロセスが終了したら connector server を立ち上げるという感じ.これでひとまず動くっちゃ動く.良かった良かった.

しかしこの方法だと起動のタイミングによっては「古いプロセスの破棄」と「新しいプロセスの完全な立ち上がり」との狭間に落ちて,リクエストを取りこぼしてしまう可能性があるので,実装に (つまり startJMXConnectorServer() を呼び出すタイミングに) 気を使う必要がありそうです.
あるいは Web Application のようなものであればこんな方法を使わずに,起動してから最初にリクエストが来た瞬間に connector server を立ち上げてやる,というような方法でも良いかもしれませんね.

こちらからは以上です.

Web Application Server を動かす時の Java8 起動オプションのメモ

java

一般的な Web Application Server *1 を Java8 で動かすにあたって,最近有効にしている起動オプションについてメモ.
何か間違っていたり,あるいは「こっちの方が良い」みたいなのがあれば教えて下さい.

-server

server mode で起動させる (指定しないと client mode になる可能性がある,マシンスペックによってスイッチする?).

-Djava.net.preferIPv4Stack=true

If IPv6 is available on the operating system the underlying native socket will be an IPv6 socket. This allows Java(tm) applications to connect too, and accept connections from, both IPv4 and IPv6 hosts.
If an application has a preference to only use IPv4 sockets then this property can be set to true. The implication is that the application will not be able to communicate with IPv6 hosts.
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

IPv6 が使える OS では IPv6IPv4 両方のホストと接続できるが,true にすることで IPv4 ソケットだけを使うようにする.

-Dnetworkaddress.cache.ttl=0

Specified in java.security to indicate the caching policy for successful name lookups from the name service.. The value is specified as integer to indicate the number of seconds to cache the successful lookup.
A value of -1 indicates "cache forever". The default behavior is to cache forever when a security manager is installed, and to cache for an implementation specific period of time, when a security manager is not installed.
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

デフォルトでは DNS の成功結果を未来永劫キャッシュするようになっていてアレなので,0を設定することでキャッシュしないようにする.

-Dnetworkaddress.cache.negative.ttl=0

Specified in java.security to indicate the caching policy for un-successful name lookups from the name service.. The value is specified as integer to indicate the number of seconds to cache the failure for un-successful lookups.
A value of 0 indicates "never cache". A value of -1 indicates "cache forever".
https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

デフォルトでは DNS の失敗結果を 10 秒キャッシュするようになっている.0をセットすることでキャッシュしないようにする.

-XX:OnOutOfMemoryError="kill -9 %p"

OutOfMemory が発生した時に実行するコマンドを設定できる.OOM が起きたら確実に自殺してもらう必要があるので kill を発行.事と次第によっては自動でアプリケーションを再起動するようにしても良いかもしれない.
Ref: OutOfMemoryErrorが発生したときにきちんとJavaプロセスを殺す - nekop's blog

-XX:+OptimizeStringConcat

Optimize String concatenation operations where possible
http://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html

文字列連結が最適化されるとのこと (Java 8 ではデフォルト true っぽい).

-verbose:gc
-Xloggc:/var/log/gc/gc.log.`date +%Y%m%d%H%M%S`

gc log を吐くようにする.またそれをどこに吐くかの設定.
log ファイル名のうしろに日付情報を付与することでlogが上書きされるのを防いでいる.

-XX:+UseGCLogFileRotation

gc log を1つのファイルに延々吐き続けるのではなく,ログローテーションを有効にする.
後述の XX:NumberOfGCLogFiles と XX:GCLogFileSize に関係がある

-XX:NumberOfGCLogFiles=5

ログローテーションさせるときに,最大何個のログファイルでローテーションさせるかの設定.数字 (ファイル個数) は任意.

-XX:GCLogFileSize=1024MB

gc log 1個あたりの最大容量 .容量は任意.

-Xms 2G

ヒープ領域の初期値.値については各アプリケーションごとにチューニングの余地がある.

-Xmx 2G

ヒープ領域の最大値.値については各アプリケーションごとにチューニングの余地がある.

-Xmn 512m

New 世代領域サイズ.値については各アプリケーションごとにチューニングの余地がある.

-Xss 256K

スレッドスタックサイズ.値については各アプリケーションごとにチューニングの余地がある.

-XX:MaxMetaspaceSize=128M

!!! これめっちゃ重要 !!!
Java 8以降だと,かつての「-XX:MaxPermSize」の代わりに設定してやる必要がある.
Permanent 領域は最大容量が有限だったのに対し,Java 8 以降の Metaspace 領域はメモリの許す限りモリモリ消費できてしまうので,最大容量を指定してキャップすることで安全側に倒してやる必要がある.
Ref: http://equj65.net/tech/java8hotspot/

-XX:+UseConcMarkSweepGC

Use concurrent mark-sweep collection for the old generation

CMS GC を使う.
Java のバージョンによってデフォルトのGCが変わりそうなので,設定したほうが安全な感じがした.

-XX:+CMSParallelRemarkEnabled

メジャーGCのRemarkフェイズをマルチスレッドで実行
(「動作させるマシンのCPUが2個以上かつ物理メモリが2Gbytes以上の場合には、自動設定される」とあるが念のため: http://www.atmarkit.co.jp/ait/articles/0704/24/news136_2.html)

-XX:+UseParNewGC

マイナーGCをマルチスレッドで実行
(「動作させるマシンのCPUが2個以上かつ物理メモリが2Gbytes以上の場合には、自動設定される」とあるが念のため: http://www.atmarkit.co.jp/ait/articles/0704/24/news136_2.html)

-XX:+UseCMSInitiatingOccupancyOnly

CMSが開始されるトリガーは二つある。ひとつはOld領域の利用率がCMSInitiatingOccupancyFraction に到達した場合。もうひとつは今CMS走らせないと先にヒープ埋まっちゃうよね、という統計判断を元にしたトリガー。UseCMSInitiatingOccupancyOnlyを付与すると後者のトリガーが無効になる。
http://nekop.hatenablog.com/entry/20140327/1395886237

というわけで,統計判断を無効に.

-XX:CMSInitiatingOccupancyFraction=75

XX:+UseCMSInitiatingOccupancyOnly と関連.

これらのデフォルトはMinHeapFreeRatio=40、CMSTriggerRatio=80なので、CMSInitiatingOccupancyFractionのデフォルト値は92になる。つまり、Old領域が92%になったときに最初のCMS GCが行われる。オブジェクトアロケーションが激しいようなアプリケーションでは92%だと手遅れになることがあるのでCMSInitiatingOccupancyFractionは下げたほうが良い。70とか。
http://nekop.hatenablog.com/entry/20140327/1395886237

[追記]

XX:CMSInitiatingPermOccupancyFraction=percent
Sets the percentage of the permanent generation occupancy (0 to 100) at which to start a GC. This option was deprecated in JDK 8 with no replacement.
http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

JDK8 以降は非推奨とのこと……JDK 9と出会ったらまた考えましょう

-XX:+ScavengeBeforeFullGC

FullGC 走らせる前に young の GC を走らせる

-XX:+CMSScavengeBeforeRemark

If turned on, it will cause a minor collection to occur just before the remark. That's good because it will reduce the remark pause. That's bad because there is a minor collection pause followed immediately by the remark pause which looks like 1 big fat pause.
https://blogs.oracle.com/jonthecollector/entry/did_you_know

CMS の Remark 処理の前にマイナーの scavenge をするということらしい.

-XX:+TieredCompilation

階層型コンパイルを有効にする.有効にすると,コンパイル済コードの実行中に JVM が最適化のための統計情報を収集可能にする.

この改良の有効性はそれほど高くはないと思われる。サーバのスタートアップ時の処理時間は,デスクトップやアプレットベースのアプリケーションほど重要ではないからだ。ただし非常にダイナミックなシステムにおいて各ノードがすばやく立ち上がる必要のある場合などには,この変更でウォームアップ時間を短縮できるかも知れない。
http://www.infoq.com/jp/news/2011/11/java7-u1

とのことだが……

-XX:+UseCompressedOops

Enables the use of compressed pointers (object references represented as 32 bit offsets instead of 64-bit pointers) for optimized 64-bit performance with Java heap sizes less than 32gb.

XX:+UseCompressedOops オプションを使用すると、Java オブジェクトヒープのサイズが 32 ギガバイト未満の場合に、64 ビット JRE のパフォーマンスを向上させることができます。この場合、HotSpot はオブジェクト参照を 32 ビットに圧縮して、処理する必要のあるデータの量を減らします。

Ref: http://itdoc.hitachi.co.jp/manuals/link/has_v101000/0342020D/0766.HTM
Ref: http://www.oracle.com/technetwork/jp/articles/java/compressedoops-427542-ja.html
Ref: http://d.hatena.ne.jp/quitada/20120130/p1

-XX:+PrintGCDetails

gc log に詳細な情報を吐き出すようにする.GC algorithm や JVM のバージョンによって追加される情報は変わってくるようだが,とりあえず GCスループットの情報が出たりする.

-XX:+PrintGCApplicationStoppedTime

Stop The World が起きた時,stop した時間を gc log に書き出すようにする.

-XX:+PrintGCDateStamps

gc log の各行の先頭に日時のスタンプを付与する (PrintGCTimeStamps にすると,JVM が起動してからの秒数が先頭に付与されるようになる.ヒトが読みにくい気がするので Date に)

-XX:+PrintTenuringDistribution

young領域からold領域への昇格(昇格?)に関するログを出力するためのオプション.

-XX:+HeapDumpOnOutOfMemoryError

OOM Error が起きた時に Heap Dump を吐き出すようにする.

雑感

Xms,Xmx,Xmn 及び Xss は割と勘と経験で設定している感じがあるので,改善の余地が残されている気がする.
以下の記事なんかを参考にするのが良いのか;

JVMのチューニング - ITエンジニアとして生きる
[調査]JVMのスタックサイズについて - Akira's Tech Notes
チューニングのためのJava VM講座(後編):ガベージコレクタの仕組みを理解する (2/2) - @IT

*1:一般的の定義が曖昧

開発に使えるダミーのマイナンバーについて



というわけでこれです

f:id:moznion:20160307122343p:plain
(オリジナル: https://www.kojinbango-card.go.jp/, https://www.kojinbango-card.go.jp/shared/images/header/main_img.jpg)

1234 5678 9012 がダミーとして使えることがこの度わかりました.良かった良かった.
チェックディジットが不正なので駄目そう.

我々はどうしたら……

快適な連打機能を提供するためにサーバサイドが出来ること

人間生きていると高確率で連打機能を提供するシステムを構築する必要が出てくることがあります.
例えばあるコンテンツについてボタンを連打することで「良いね」を表明するようなシステムです.

連打は楽しい!! しかし実装する方としては純粋に楽しんでばかりはいられません.
こうしたシステムは素朴に実装したとしてもある程度のトラフィックまでは耐えられるかもしれませんが,ある規模を超えると安定して機能提供する事は難しくなってくるかもしれません.

ここでは,サーバサイドの話題を中心として,快適な連打機能を提供するシステムをどうすれば提供できるかを考えていきます (あくまで一例です).

想定としては,

  • あるコンテンツについてボタンが付いていて,そのボタンは連打が出来る
  • あるコンテンツについてボタンが何回押されたかを取得できる

というシステムを仮定します.

なんとなく結論が分かる雑な図

f:id:moznion:20160222000426p:plain

本題

サーバを分離する

API を通じてやり取りするアプリケーションを考えた時に,通常の API を提供するサーバと連打可能な API を提供するサーバを分けるという方法が考えられます.
これらのコンポーネントが同居していると,連打機能の影響でシステムの負荷が上昇した時に通常の API まで巻き込んでしまうので,分離させておくことでそうしたリスクを排除することが可能となります.
また,それぞれのコンポーネントについてインスタンスを適宜追加投入することでスケールアウトが可能となるので,負荷が高まった時に柔軟な対応を取ることができるようになります.

リクエスト自体を少なくする

連打可能なボタンを設置した時に,1回ボタンが押されるごとに API が呼ばれては大変です.
クライアント側でそのイベントをバッファしておき,あるタイミングでそれらをまとめて1つのリクエストとして扱うといった方法をとることで,総リクエスト数を減らすということが出来ます.リクエストを減らすというのは,負荷に対する根本的な解決の1つと言えるでしょう.

こういう風にバッファリングすることでリクエスト数を減らすという方法はクライアント側の実装にある程度の負担を強いることとなりますが,そこは開発のコストのバランス感という感じでしょう.

また,不正行為への対策も必要となる場合があります.例えば,「あるタイムウィンドウ内に何回押されたか」を束ねて1リクエストにする方法を考えた時に,「何回押されたか」をユーザの手によって詐称されて “LONG_MAX” なんかを送られてきた日には目も当てられません.こうした異常な入力に対してはサーバ側で何らかの対策を取る必要が出てくるでしょう.

高速なストレージを使う

memcached や Redis というような高速なストレージを利用するという話題です.

ある程度の規模感までは RDBMS でも十分かもしれませんが,連打可能なサービスは往々にして I/O が増えがちとなるのでメモリストレージを利用できれば考えることが減り,楽になるように思います (あくまで要件に依る.ミッションクリティカルな要求だったら難しい選択になる).

また,メモリストレージを使うのであればバックアップについて考えておく必要が出てくると思います.メモリストレージに全ての信頼を寄せるのであれば不要かもしれませんが,僕は心が弱いので特定のタイミングで Redis に乗っているデータを MySQL に掃き出すということをやっています.

一言で言うと,ユーザからリクエストを受ける時は高速なストレージで,そして適時バックアップを RDB に取ってデータの消失を防ぐ,という戦略です.

データ構造をシンプルにする

上記のメモリストレージを使うという話題にも共通するトピックです.
データ構造が複雑であれば複雑であるほど,メモリストレージに乗せにくくなり,またカウントをシュッと取得することが難しくなります.
いかに格納を高速にするか,いかに参照を高速にするかはデータ構造のシンプルさにかかっていると言うことができます.
例えばこの記事で想定しているシステムだと,シンプルに「ボタンが何回押されたか」というカウント情報のみをストレージに保持するなどといった構造を考えることが出来るでしょう.

解析用のストレージは別に用意する

とはいえ解析はしたいので,そうした解析に使えそうな詳細なデータは非同期的に *1 別の解析用のストレージ (例えば BigQuery や Redshift,あるいは自前の Hadoop など) に放り投げることで,データを捨てずに貯めておき後で解析をするという方法が考えられます.
さて,解析をリアルタイムで行いたいという要件は稀だと思われるので,データを貯めるだけ貯めておいて後で解析をするという方法を採っていますが,もし「リアルタイムに解析したい!」という要件が出てきたら色々考える必要が出てきます.難しいですね.

まとめ

連打できる要素が用意されると人間は連打をしがちです.基本的に連打という営みは楽しいからです.
快適な連打機能を提供するためには色々な工夫をする必要があり,本記事では以下のようにその一例について述べました.

  • サーバを分離する
  • リクエスト自体を少なくする
  • 高速なストレージを使う
  • データ構造をシンプルにする
  • 解析用のストレージは別に用意する (そして非同期で格納する)

とは言え「誰が,いつ,何回押したか」をリアルタイムかつランキング形式で出したい!!! などといったクレイジーな要件が出てくるとこの記事で書いたようなシンプルな方法では難しくなるので,更に考える必要が出てきます.世の中は難しい.地獄じゃ.

また,本記事では非同期プログラミングや HTTP/2 に関する話題については触れませんでしたが,こうした技術は快適な連打を支える一助になるかもしれません.


快適な連打を支えるにはここで書いた他にももっと効果的な方法があることと思います.何か知っている方がいたら語らいましょう.

*1:同期的にやるとその I/O 処理に引きずられてしまう

管理画面まわりの事情,負の UI/UX について

管理画面を作ってると,「みだりに押されたくはないが,かといって無いと不便」みたいなボタン (或いはそれ以外の何か) を画面上に置きたくなる時があると思う.
例えばバッチで行うような処理を (なんらかの事情で) 即時実行するボタンのようなやつ.こういったボタンは便利だけれど,無闇に押されるとシステムに対する負荷が上がり,サービスの提供に悪影響を及ぼす場合があるのでなるべく押しにくくあるべきだと思う.
そういった,おもてなしを目的とはせず,むしろ或る操作に対する敷居を高くするための,謂わば負の UI・負の UX をどうするべきかを軽く考えたのでここに記す.

警告を出す

例えばこの画像のような感じ.
f:id:moznion:20160214162920p:plain
こうして注意を喚起することで精神に注意を促すくことで,みだりな利用を抑制するという方法.ボタンを赤くすることで危険な処理であることも表している.
加えてボタンが押された時に念を押すための confirm ダイアログを出すというのも良さそう.
この方法は素朴かつ簡単だけれど,効果は薄いと思う.最初のうちは注意を喚起することができるかも知れないけれど,人間は時間が経つにつれて慣れてくるので,次第に無意味な警告文と化す可能性が高い.
とは言え,「押さないでくれ!」というエクスキューズにはなると思う.

ボタン自体を押しにくくする

例えばボタンの要素のサイズを小さくして押しにくくするとか,画面を render する度にボタンが表示される場所がその時々で変わるとか,ボタンがランダムに動くとか.とは言え,これはただ単純に押しにくくなるだけで,それが「注意しなければならない処理」かどうかは分かりにくい気がする.単純に腹が立つインターフェイスかもしれない.
そういった手法よりもむしろ,(1人でもできるけれど) 人間が2人以上いないとボタンが押しにくい,みたいなインターフェイスの方が良い可能性がある.例えば,キーボードの asdfjkl: キー全てを押していないとボタンがアクティブにならないとか.核兵器の起動スイッチなんかは,2人の人間が互いに鍵をひねりながらスイッチを押すみたいな,そういう仕組みだった気がする.1人では容易に押せないことで注意を要する処理であることがなんとなく分かる感じがするし,仮に2人以上で実行するとしても単独の人間の判断ではなく複数の人間の合意を得て実行することになるのである種の抑制が効く可能性が高まる.とは言え,割と凝った仕組みなので実装がだるいというのがある.コスト感と相談する必要がありそう.

要素自体を hidden にする

これは我々がよくやる方法で,容易に操作させたくない要素を <div hidden></div> でくくることで見えなくしてしまうという荒業.もしもその操作を行いたい場合は,Developer Tools 等を使って該当する hidden を削除し,要素を露出させることで処理を行える状態にするという方法.
基本的にその隠し div の存在を知らない人は使うことが出来ないし,Developer Tools を使えないタイプの人にも無闇に使われないので,割と安全という感じがある.が,「隠し機能」という体になってしまうので運用する人の間で口伝でしか伝わらないので,どこかで知識の断絶が起きてしまう可能性もある.

最近やったやつ

警告文が震えることで危険を喚起している.人間,高速に動くものに対しては警戒するものである.
本来,こういう処理は css animation を使ってやるべきなのだろうけど,今回は要素幅を固定した上で<marquee behavior="alternate" scrollamount="100"> という風にすることで,高速に要素を往復させるというバッドノウハウで実現した.ボタンを震えさせても良かったかもしれない.


まあ,このように要素を震えさせるのは悪い冗談としても,ボタンを押しにくくする為の負の UI をどうしていくかを考えていく必要があるように思う.どうすべきか.


[追記]
良さそう

管理画面まわりの事情,負の UI/UX について - その手の平は尻もつかめるさ

GitHubリポジトリ削除するときにリポジトリ名要求されるのが参考になるかも? と思いました

2016/02/14 17:31
b.hatena.ne.jp

Guice で generics の仮型引数の実体 class (ParameterizedType) を上手いこと inject する

java

タイトルだけでは何を言っているかよくわかりませんが,

public class Foo<T> {
    private Class<T> clazz;

    ...

    public void something() {
        // 例えばここで clazz を使って何らか処理をする
        System.out.println(clazz);
    }
}

みたいな class があるときに,ここの clazzT の実際の class (ParameterizedType) を上手いこと inject したいと言う話題です.


結論から言うと,TypeLiteral<T> を使うと所望の動作を実現できます.

public class Foo<T> {
    private Class<T> clazz;

    @Inject
    @SuppressWarnings("unchecked")
    public Foo(TypeLiteral<T> type) {
        this.clazz = (Class<T>) type.getRawType();
    }

    public void something() {
        // 例えばここで clazz を使って何らか処理をする
        System.out.println(clazz);
    }
}

こういう風に TypeLiteral を inject してから,TypeLiteral#getRawType() を使うと ParameterizedType を取得することが可能となります.
でもって,

public class App {
    @Inject
    private Provider<Foo<Integer>> foo;

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
            }
        });
        App main = injector.getInstance(App.class);

        main.foo.get().something(); // => class java.lang.Integer
    }
}

というように使うと,コード中のコメントで書いたように class java.lang.Integer,つまり T の実態の class が inject されている様子を見れてめでたしめでたし.

この記事の例では Provider<T> と Constructor Injection でやりましたが,bind() を使う方法でも実現可能でしょう.

補足

この記事の例だと何が嬉しいか良くわからん感じですが,どこかで役に立つタイミングが来るはず…… (というか今日実際に役に立つタイミングが来たのだけれど,その例をそのまま出すわけにもゆかないし,汎用的な例を考えたものの良いのが思いつかなかった)

chat ops について

chat ops について,何が嬉しいのかとか,その辺を上手く言葉にできていなかったので言葉として記しておくこととします.
chat ops のメリット・デメリットなんかは先達によって散々語り尽くされていると思うところですが,自分の思考をまとめるために……


chat ops というのは基本的に「chat を共通のコマンドライン」と見なして,その上で様々な作業,例えばデプロイなんかを行なうことを指すと思います.
「共通のコマンドライン」はチームの知識が詰まった道具箱であり,それを共有することは大きな力になると思います.
その「共通のコマンドライン」で作業することの何が嬉しいかというと,最も大きなものでは「作業の属人化を避けることができる」というものが挙げられるでしょう.
作業の属人化を避けるというのはすなわち誰でも作業可能になるということで,つまりこれは「お願いします脳」に陥ることを防ぐ大きな助けになる事を意味すると思います.
(とは言え,それはそのチャットを介して行われる作業がブラックボックス化されているというだけということでもあり,本当の属人化を防ぐためにはその裏側に対する理解を深める必要があるとも思います.例えばデプロイだったらチャットを介して呼ばれるデプロイスクリプトを読むなど)


ところで,「コマンドライン」といえば入力するだけではなく,その出力を加工して新たな副作用を生むことも可能です.
chat ops でも同様に,能動的にコマンドを発行するだけでなく,チャットに流れる文言を受けて,その内容に基づいて任意の処理を実行させることが出来ます (というか仕組み的には,能動的なコマンド発行に見えているものも,一旦その命令 (メッセージ) がチャットに書き込まれて,その「チャットに流れた命令」を bot やらなにやらが解釈して対応する処理を行っているので当然の話).
例えば,バッチ処理の開始時や終了時にその結果をチャットに流して,そのチャットメッセージに引っ掛けてメールで投げる,みたいなものが考えられるでしょう.

挙げた例はメール送信処理というものでしたが (あまり汎用的な良い例を思いつかなかった……),もっとプライオリティの低い (失敗しても構わないような,しかしあると便利な) 処理であれば,できるだけその処理をプロダクションコードに含めたくないというのが心情というものです.
万が一そのプライオリティの低い処理が失敗してしまったばかりに,他の重要な処理を巻き添えにして死んでしまっては目も当てられません.
そこで,そういった処理の本体はプロダクションコードに含めずに,その代わりにイベントに応じたチャットのメッセージにフックさせることで,他のプログラムに優先度の低い処理を委譲してしまうという方法が考えられると思います.
そうすることで,その「他のプログラム」がもしも失敗してしまっても,処理の本体は巻き添えを食わずに処理を継続することが可能となります.
つまりチャットを,プロダクションコードを一切いじること無く挙動を拡張することを可能にするハブのような存在として扱うことで,変更による破壊から身を守ることが出来るようになるという感覚です.この機構に乗ることによって,プロダクションコードを気にせず安心して挙動を拡張することが可能となるというメリットを享受することができます.

とは言え,何でもかんでもチャット経由で処理させようとすると処理の内容がプロダクションコードと chat ops 用のコードとで分散してしまって良くないので,適切な分離を心がける必要はあると思います.


また,chat ops につきまとう難題として権限分けの話題があると思っていて,ここについては何か良い方法があるのかどうかよく分かっていません.
誰でも彼でもデプロイ出来てしまえばそれは問題ですし,チャットに流れてくる内容をあらゆる所に無権限でフォワードされても困ってしまいます.
User ID で縛るロジックを chat opsスクリプトに仕込むみたいな素朴な方法がパッと思いつきますが,あまりにアドホックというか……
(この辺を上手く扱えるようなチャットのサービスってあるんですかね?)


後,これは当然のことですが,こうしたチャットを介して処理を行なう際はチャットが SPOF になってしまうので,
チャットが死んだ時に運用不能になるような致命的な処理はここでは行なうべきでは無いと思います.


まあ,色々書いた上で身も蓋もない話ですが,コマンドラインでやれば良いものはコマンドラインでやれば良いし,GUI でやれば良いものは GUI でやれば良いし,チャットでやれば良い物はチャットでやれば良いと思います.適材適所で.
chat ops をしたいからと言って,本来不要なメッセージまでチャットに流してしまうのは本末転倒という感じがありますので……