Play2.3のPlugin
Scala + Play framework 2.3 ってとこでアルバイト初めて一月半ぐらい経とうとしてる。 OOSC読んだり、ポスター発表してたりもしたけど落ち着き始めたので、Playの中身ちゃんと知ろうとか考えてる
Playで設定を読み込み、リクエストを受け付ける前にする云々、特にPluginを見る。 PlayはPluginという機構を使って様々な前準備及び終了時の後処理を行う。 (コレ以外にもDBPluginなど直接Application.pluginを用いて呼び出す事も出来るが今回の範疇ではない)
playframeworkが提供している様々なPluginについて見て行きたいと思うが、本題に入る前にPluginとは何なのかにあたる部分について触れる
ちなみに2.4からはPluginの機構は使われなくなるので、これは2.3.xまでの話。 (さすがにまだ2.4系を本番で使ってる人はいない...?) 一応2.4系はplay moduleってのをDIしてく、って世界観らしい
以下で触れるのは2.3.xブランチのこのcommit上の話、ただコードを読むだけ
前振り
PlayApplication
- consoleとかで使える
StaticApplication extends ApplicationProvider
- Test Mode
play.core.TestApplication extends ApplicationProvider
- Dev Mode
play.core.ReloadableApplication extends ApplicationProvider
これらの中でPlay.start(application)
が呼ばれてる。
startの中では、routesを遅延評価で構築したり、pluginを順に読み込んだりしている(読み込むpluginについては後述)。
applicationはplay.api.DefaultApplciation
などが用いられ、色んな設定とかこいつが用意してる
play.api.DefaultApplication
class DefaultApplication( override val path: File, override val classloader: ClassLoader, override val sources: Option[SourceMapper], override val mode: Mode.Mode) extends Application with WithDefaultConfiguration with WithDefaultGlobal with WithDefaultPlugins
Application
, WithDefaultConfiguration
, WithDefaultGlobal
, WithDefaultPlugins
で成り立ってる
Applicationの主要な構成要素
- Applicationのルートとなる
path
- Globalなどのクラスを読み込むための
classloader
- エラー時にブラウザにソースを載せるために用いられる
sources
- 実行時のmode(Dev/Test/Prod)を示す
mode
- Global.scalaから得られる
global
- これは後ほど述べるpluginsの重み10000で実行されるものとなる
getControllerInstance
とかあったのか
- application.confなどを読み込み生成される
configuration
- アプリケーション実行時などに利用される
plugins
- 後のPlay.start時に評価されるルーティングを保持する
routes
- loggerの設定
- エラーハンドル
- そして
plugin
メソッドによる別途Pluginの呼び出し(今回の対象外) - ...
- Applicationのルートとなる
WithDefaultConfigurationとは
Application
をself type annotationとして、上記configuration
を読み込むtraitcom.typesafe.config.Config
軽くをラップしたものを利用している
WithDefaultGlobalとは
WithDefaultConfiguration
を持つApplication
をself type annotationに持つtraitconfiguration
に書かれた設定からGlobalPluginとなるクラスを探す- 特に指定がなければ基本的なエラー処理等のみが定義された
GlobalSettings
をそのまま用いるDefaultGlobal
が用いられる globalInstance
ではjavaGlobal
を取りにいき、無ければscalaGlobal
を取りに行く- 得られたものはGlobalPluginとして、実行される
WithDefaultPluginsとは
- 設定ファイル(play.plugins)に書かれた文字列から各種Pluginをclassloaderを用いて用意する
- 自身が定義したPluginや、先に説明したGlobalPlugin、その他様々なライブラリが提供するPlayのPluginを用意する
playframework/playframeworkのプロジェクトにはいくつものサブプロジェクトなども含まれているが、通して以下のようなplugin設定ファイルが存在する
./framework/src/play/src/main/resources/play.plugins ./framework/src/play-cache/src/main/resources/play.plugins ./framework/src/play-java/src/main/resources/play.plugins ./framework/src/play-java-ebean/src/main/resources/play.plugins ./framework/src/play-java-jdbc/src/main/resources/play.plugins ./framework/src/play-java-jpa/src/main/resources/play.plugins ./framework/src/play-java-ws/src/main/resources/play.plugins ./framework/src/play-jdbc/src/main/resources/play.plugins ./framework/src/play-ws/src/main/resources/play.plugins ./framework/test/integrationtest/conf/play.plugins
Pluginについて
ここまでで、設定や各種Pluginを使う用意が出来た。 では、Pluginとは何なのか。
見やすいようにdocumentを削ったりしたが、以下のような宣言がなされている。 全てのPluginはこのPluginを実装している。
trait Plugin { // Called when the application starts. def onStart() {} // Called when the application stops. def onStop() {} // Is the plugin enabled? def enabled: Boolean = true }
そして、Play.start
の中でonStart()
が、Play.stop
の中でonStop()
が順に実行される。
ここで順に、と述べたのば、pluginの設定ファイルの行頭についた数字がpriorityとなり値の小さい順にonStart()
では実行される。
逆にPlay.stop
では数字の大きい順にPluginのonStop()
が実行される。
Playframeworkのドキュメントの中でも説明がある通り、他のPluginに依存するPluginを作る際にはこのpriorityに注意する必要がある。 特に他のPluginに影響を及ぼさないPluginならば10000より大きいpriorityを設定するのが良い。
実行前には各種pluginが設定ファイルなどを眺めてenabledとなるもののみが実行される。
例えばなぜかEhCachePluginが入った状態で、mumoshu/play2-memcached - GitHubを使いたい場合には、READMEにある通り、application.confにenhcacheplugin=desabled
と書いてEhCachePluginが実行されないように出来る。
各種Pluginを眺める
libraryDependenciesに何も書かないPlay projectの場合./framework/src/play/src/main/resources/play.plugins
に書かれたPluginのみが実行される。
jdbcをlibryDependenciesに含めた場合には./framework/src/play-jdbc/src/main/resources/play.plugins
に書かれたPluginも実行される。
1つ1つ見ていくわけだが、全部見るのは億劫なので良く使うものだけ今回は眺める。
==> ./framework/src/play/src/main/resources/play.plugins <== 1:play.core.system.MigrationHelper 100:play.api.i18n.DefaultMessagesPlugin 1000:play.api.libs.concurrent.AkkaPlugin 10000:play.api.GlobalPlugin ==> ./framework/src/play-cache/src/main/resources/play.plugins <== 600:play.api.cache.EhCachePlugin ==> ./framework/src/play-jdbc/src/main/resources/play.plugins <== 200:play.api.db.BoneCPPlugin 500:play.api.db.evolutions.EvolutionsPlugin
こんなとこ
1:play.core.system.MigrationHelper
2.2.xにはなかったけど2.3.xになって増えたもの。
名前の通り、Play frameworkのversion移行時に設定ファイルの単位が変わった事で挙動が変わりそうな事を、起動前にチェックしwarningを出してくれる。
2.3.xへの移行時にチェックしてる内容はsession.maxAge
の単位が数値(integer)から期間表記(e.g. 1h 30m)になる事である。
100:play.api.i18n.MessagesPlugin
多言語用Pluginで、Lang.availables
でconfから有効なlangを取得し、多言語化用の変換Mapを用意する
有効なlangを用いたmessages.lang
、messages
、messages.default
などをパースして後者優先で上書きしたMapを作る
translate時はこいつを見てkeyを渡し、java.text.MessageFormat
に食わせて変換結果を返す
MessageFormatがそのまま使われるからPlayframework の I18N で 1st, 2nd, 3rd, 4th, ... - Qiitaみたいなことも出来る
200:play.api.db.BoneCPPlugin
こちらのissueでHikariCPとどちらを使うかを争って、以前使われ続けてるBoneCP。 (知らなかったけど1週間前にこんなコメント増えてた)
DBへのコネクションを取得出来るDataSourceを作るのが目的
onStart()
では設定ファイルの情報を基に、DBのコネクションを生成出来るかをチェックしている。
チェックする時にBoneCPApiオブジェクトが作られるわけだが、作られる際にDataSourceを作り、DBのコネクションプール周りの設定を入れまくったConnectionHookをこいつに登録している。
BoneCPの場合javax.sql.DataSourceを継承したBoneCPDataSourceを持っており、こいつからコネクションを取得する。
BoneCPDataSourceはBoneCP(ConnectionPool)を持っており...、これ以上は今回は関係ない
onStop()
では全てのDataSourceを閉じる処理を行ってる、なるほど
ちなみに...
slickのplay用Pluginplayframework/play-slick...play.plugins - GitHub*1はpriorityが400,401となっており、この箇所で実行される。
400の方では、(Playから使えるコネクション生成出来る)Databaseオブジェクトを全てclearして、401の方ではevolutionを生成している。
"# --- Created by "
というコメントで始まるかどうかでevolutionをするかを判定してるのが意味わからん。
が、スキーマが変わる毎にevolutionを自動生成してくれるのはありがたい
ちなみに確認ではあるが...
コネクションを取得するのはplay.api.DB.getConnection
からApplicaiton.plugin[DBPlugin].map(_.api.getConnection(...))
とした時などに行われ、処理が終了した時に開放される。
500:play.api.db.evolutions.EvolutionsPlugin
priorityが500未満のPluginでevolutionファイルが生成されているはずである。 もしevolutionを吐くpluginを書くなら500未満にしなければ動かない。
設定のevolutions.use.locks
を見てあればロックしながら、無ければロックせずにevolutionを実行する*2。
evolutionの管理はplay_evolutions
というテーブルを勝手に作って用いている。
これらはonStart()
で行っており、onStop()
では何もしていない。
600:play.api.cache.EhCachePlugin
お手軽メモリキャッシュ、EhCacheのためのPlugin。
初めにehcache.xmlのような設定ファイルを読みつつ、保存先を管理するCacheManagerを作成し、playと名づけた保存先を作る。
*3
onStop()
では管理してるCacheManagerそのものを破棄してる。
1000:play.api.libs.akka.AkkaPlugin
onStart()
は何もしない。
最初のAkka.system.actorOf[Props[MyActor]]
のような呼び出しがあった時に初めてActorSystemが作られる。
ログにも出るんだけど、GlobalPluginのonStartでスケジューラなど起動してるから気が付かなかった...
*4
`onStop()はActorSystem自体を閉じる
10000:play.api.GlobalPlugin
onStart()
時にはユーザが定義したGlobalのonStart()
を中で呼び出す。
onStop()
時も同様にユーザが定義したGlobalのonStop()
を中で呼び出す。
ちなみにユーザ定義のGlobalにだけbeforeStart()
という関数があるのだが、これはGlobalPluginオブジェクトが生成された時に実行されるため、上記で上げたような各種PluginのonStart()
が実行される前に行う処理を定義出来る。
逆にこの中ではDBに対する処理などは行えないので注意。
締め
2.4系にあげる前にplay moduleも読むか、play-core読むかNettyあたり読みたい