パーソナルツール
現在の場所: ホーム Users YAMADA Masaki 2007 0710 pluginの仕組み

pluginの仕組み

— カテゴリー:

2007年10月31日, 第4回grails code readingでGrailsのプラグインの仕組みについて, ざっとコードを見ながら解説しました.

プラグインは grails に機能を追加する仕組みです. grails では標準で用意される機能も基本的にはプラグインとして実現されています. つまり, grails はプラグインの集合と考えることができます.

プラグインの仕組みは src/commons/org.codehaus.groovy.grails.plugins に, デフォルトで提供されるプラグインは src/groovy/org.codehaus.groovy.grails.plugins にあります.

デフォルト・プラグイン

後者のディレクトリにある XXXGrailsPlugin.groovy がプラグインの実体です. ここでは以下のようなプラグインが標準で提供されていることが分かります.

もちろん, 同じようにしてカスタムなプラグインを作成して組み込むことができます.

GrailsPlugin

各プラグインを規定しているのが GrailsPlugin (インタフェース) です.

GrailsPlugin

GrailsPlugin の実装クラスは AbstractGrailsPlugin (抽象クラス) で, 通常はその具象サブクラスである DefaultGrailsPlugin が使われます.

AbstractGrailsPlugin

DefaultGrailsPlugin

GrailsPluginManager

プラグイン全体を管理するのが GrailsPluginManager (インタフェース) です.

GrailsPluginManager

GrailsPluginManager の実装クラスは AbstractGrailsPluginManager (抽象クラス) で, 通常はその具象サブクラスである DefaultGrailsPluginManager が使われます (この辺りの構造は GrailsPlugin と同じで, 他の grails の主要クラスも似ています).

AbstractGrailsPluginManager

DefaultGrailsPluginManager

DefaultGrailsPluginManager を見ると, 標準プラグインは CorePluginFinder を使って "classpath:org/codehaus/groovy/grails/**/plugins/**GrailsPlugin.groovy" から, カスタム・プラグインは GrailsRuntimeConfigurator を使って "**/plugins/*/**GrailsPlugin.groovy" から作成されているものと思われます.

プラグインの中身

では,プラグインとは何ができるものなのか, GrailsPlugin を見ていきましょう.

まず定数定義がありますが, これは後回しにします.

構造

次にいくつかの getter があります.

  • getName()
  • getVersion()
  • getDependencyNames()
  • getEvictionNames()
  • getLoadAfterNames()
  • getDependentVersion(String)
  • getManager()
  • getInstacne()
  • getObservedPluginNames()
  • getProvidedArtefacts()

name, version はそのままですね.

dependency names はこのプラグインが必要とする他のプラグインの名前の配列です.

eviction names はこのプラグインが排除する他のプラグインの名前の配列です.

load-after names はこのプラグインがロードされる以前にロードされていなければならない他のプラグインの名前の配列です.

dependent version は依存する他のプラグインの名前を指定するとその依存するバージョンを返します.

manager はこのプラグインを管理する GrailsPluginManager です.

instance は対応する XXXGrailsPlugin のインスタンスです.

observed plugin names はこのプラグインが変更を見張っている他のプラグインの名前の配列です.

provided artefacts はこのプラグインが提供する artefact クラスの配列です.

これらはプラグインの構造を定義しているものと考えられます.

設定

次に 'doWith' で始まるいくつかのメソッドがあります. これらはプラグインに関わる設定を行うものです.

  • doWithApplicationContext(ApplicationContext)
  • doWithRuntimeConfiguration(RuntimeSpringConfiguration)
  • doWithWebDescriptor(GPathResult)
  • doWithDynamicMethods(ApplicationContext)

これらは GrailsRuntimeConfigurator#configure(), GrailsRuntimeConfigurator#reconfigure(), GrailsReloadServletFilter#doFilterInternal() などから DefaultGrailsPluginManager を経由して適時呼ばれています.

doWithApplicationContext() はプラグインのインストール後に呼び出されます.

doWithRuntimeConfiguration() は BeanBuilder のシンタックスを用いて, Spring の wiring を行います (詳細は後で述べますが, XXXGrailsPlugin.groovy では doWithSpring クロージャに対応します). この際, 次のような bean が設定されます.

  • application: GrailsApplication
  • manager: GrailsPluginManager
  • plugin: DefaultGrailsPlugin
  • parentctx: ApplicationContext (Spring)
  • resolver: PathMatchingResourcePatternResolver (Spring, パタン文字列から適切なリソースを見つけてくるのに使われる)

doWithWebDescriptor() は XmlSlurper のシンタックスを用いて, WEB-INF/web.xml に変更を加えるためのものです.

doWithDynamicMethods() は実行時に動的なメソッドを付け加えるためのものです. application context が渡されるので, ここからたどれる任意のクラスのメタクラスをいじることができます.

解釈

実際のプラグインは groovy で記述されています. 例えば DomainClassGrailsPlugin を見てみましょう. 普通だったら, DefaultGrailsPlugin のサブクラスにすると思いますが, DomainClassGrailsPlugin はスーパークラスを持っていません. では, このプラグインの実体 (XXXGrailsPlugin.groovy) と DefaultGrailsPlugin はどういう関係にあるのでしょうか?

DefaultGrailsPlugin のコンストラクタの宣言は次のようになっています.

public DefaultGrailsPlugin(Class pluginClass, Resource resource, GrailsApplication application)

ここで pluginClass が XXXGrailsPlugin.groovy です. 内部ではさらにこれを GrailsPluginClass とBeanWrapperImpl でラップして使っています (DefaultGrailsPlugin#initialisePlugin()).

GrailsPluginClass は元をたどると AbstractGrailsClass -> GrailsClass で, grails で実際にプログラマが書くほとんどの groovy クラスを操作するためのクラス, BeanWrapperImpl は Spring Bean として扱うためのクラスです.

実際には XXXGrailsPlugin.groovy のコードが直接走ることはあまりなく (もちろんクロージャは利用される), DefaultGrailsPlugin が XXXGrailsPlugin の持つプロパティを解釈・実行する形になっています. 例えばプラグインの version を知るためには eveluatePluginVersion() の中で, "version"(GrailsPlugin の中で VERSION という文字列定数で定義) という名前のプロパティを get しています.

最初に述べた, GrailsPlugin の最初の定数定義の多くは XXXGrailsPlugin.groovy で定義されるべきプロパティの名前を表しています. これらを解釈しているのが, evaluate で始まる以下のプライベートなメソッド群です.

  • evaluatePluginVersion
  • evaluatePluginDependencies
  • evaluatePluginLoadAfters
  • evaluateProvidedArtefacts
  • evaluatePluginEvictionPolicy
  • evaluatePluginInfluencePolicy
  • evaluateOnChangeListener
  • evaluateObservedPlugins
  • evaluatePluginStatus

実は grails の他の部分でも基本的な構造はこのようになっており, プログラマが .groovy で書いたコードが直接実行されるのではなく, それに対応するインタプリタがコードを解釈実行します.

最後に XXXGrailsPlugin.groovy で定義して意味のあるプロパティをまとめると以下のようになります.

  • doWithDynamicMethods: 既出, クロージャ・プロパティ
  • watchedResource: 後出, プロパティ/静的プロパティ/フィールド
  • evict: 既出, プロパティ/静的プロパティ/フィールド
  • status: 'enabled' または 'disabled' (大文字小文字問わず), プロパティ/静的プロパティ/フィールド
  • influences: 後出, このプラグインが影響を与える他のプラグインの名前の List, プロパティ/静的プロパティ/フィールド
  • onChange: 後出, 変更があったときに実行されるクロージャ, プロパティ/静的プロパティ/フィールド
  • onConfigChange: 後出, 構成に変更があったときに実行されるクロージャ, プロパティ/静的プロパティ/フィールド
  • doWithWebDescriptor: 既出, クロージャ・プロパティ
  • version: 既出, プロパティ
  • doWithSpring: 既出, クロージャ・プロパティ
  • doWithApplicationContext: 既出, クロージャ・プロパティ
  • dependsOn: 既出, Map, プロパティ/静的プロパティ/フィールド
  • artefacts: 既出, List, プロパティ
  • providedArtefacts: 既出, Collection, プロパティ/静的プロパティ/フィールド
ここで「プロパティ/静的プロパティ/フィールド」とある場合, メソッドやクロージャでも評価してくれないことに注意. 全体にあまり統一されているとは言い難い.

変更監視

XXXGrailsPlugin.groovy で watchedResources (String あるいは List<String>) があれば, そこで指定したリソースが変化したときに onChange クロージャが呼ばれます (DefaultGrailsPlugin#checkForChanges).

同じようにこのプラグインがリロードされた場合, influences で指定したプラグインもリロードされます.

メタ情報

プラグインを作成する (% grails package-plugin) と, プラグインに関するメタ情報を記述した plugin.xml が生成されます. PluginMetaManager (インタフェース), DefaultPluginMetaManager (実装クラス) は, そのメタ情報を扱うものです.

PluginMetaManager

DefaultPluginMetaManager

マネージャの仕事

PluginManager の役割はプラグイン全体を束ねることにあります.

まずは自分の管理下にあるプラグインに対する基本操作です.

  • GrailsPlugin getGrailsPlugin(name)
  • GrailsPlugin getGrailsPlugin(name, version)
  • boolean hasGrailsPlugin(name)

これらはほぼ自明ですね.

次にプラグインのロード, 初期化などの仕事です. またこれらと密接に関連するのが, 変更伝播の仕組みです.

  • void loadPlugins()
  • boolean isInitialised()
  • void refreshPlugin(name)
  • void checkForChanges()
  • Collecetion getPluginObservers(GrailsPlugin)
  • void informObservers(name, event)

loadPlugins(), アプリケーションの GroogyClassLoader (通称 gcl) を使って, コア・プラグイン (標準で提供されるプラグイン) がユーザ・プラグイン (ユーザが追加したプラグイン) より先になるようにロードして, 登録します. この過程で, eviction, delayed loading, observed plugin の登録などの処理も行われます.

また, ここで conf/Config.groovy に plugin.include, plugin.exclude エントリがあれば, それらもフィルタリングされます.

次にファサードとして, 管理下にあるプラグインの操作を呼び出す仕事があります.

  • void doRuntimeConfiguration(RuntimeSpringConfiguration)
  • void doPostProcessing(ApplicationContext)
  • void doWebDesecriptor(...)
  • void doDynamicMethods()

doRuntimeConfiguration() は各プラグインの doWithRuntimeConfiguration() を, doPostProcessing() は各プラグインの doWithApplicationContext() を, doWebDescriptor() は各プラグインの doWithWebDescriptor() を, doDynamicMethods() は各プラグインの doWithDynamicMethods() を, それぞれ呼び出しています.

最後にアーティファクトの管理があります.

  • void doArtefactConfiguration()
  • void registerProvidedArtefacts(application)

アーティファクトは GrailsApplication が管理しているので, 各プラグインが提供するアーティファクトをアプリケーションに登録したり, 各プラグインの doArtefactConfiguration() を呼んだり (これは結局 ArtefactHandler をアプリケーションに登録) します.

UML

plugin classes