技術者向けテクニカルtips
 / 

OSGiアノテーションとは?その紹介と活用法

OSGiアノテーションの紹介と活用方法をご紹介。

はじめに

こんにちは。
日本ライフレイ、サポータビリティエンジニアの竹生(たけお)です。

本記事は、以下の知識レベルの方たち、ユースケースが対象です。ご参照ください。


■ 本記事対象の知識レベルとユースケース

  • 中級レベル
  • 一般的なLiferay DXP開発に必須

*レベルの定義については、こちらのブログの冒頭で紹介しています。


Liferay 7CEやLiferay DXPコードのレビューをしていると、さまざまな種類のアノテーションを目にする機会があるかと思います。初めてそれらを見ると、その種類の多さに圧倒されてしまう方も多いのではないでしょうか。そこで本記事では、代表的なアノテーションの説明と、それらをいつOSGiコードで使用するか、簡単にまとめてご紹介します。

前編で紹介したアノテーションもあわせて御覧ください。

@Component

 


※本記事はLiferay Community Blogに投稿されている”Liferay/OSGi Annotations - What they are and when to use them”を翻訳したものです。配信元または著者の許可を得て配信しています。

OSGiの世界で、@Componentはサービス実装を定義する重要な「宣言型サービス」アノテーションです。「宣言型サービス(DS)」とは、サービスを動的に宣言するためのOSGiの一面であり、他のコンポーネント同士を繋げられるようにするものです。

このアノテーションには、主に以下3つの属性があります。

  • immediate - 基本はtrueに設定します。これによりコンポーネントがすぐに開始され、参照の解決や遅延起動を待つ必要がなくなります。
  • properties - コンポーネントにバインドするOSGiプロパティのセットを渡すために使用されます。該当のコンポーネントはもちろん、他のコンポーネントもプロパティを見ることができるようになります。これらのプロパティはコンポーネントの設定に役立ちますが、コンポーネントのフィルタリングの際にも役立ちます。
  • service - コンポーネントが実装するサービスを定義します。場合によってはオプション(必須ではない)ですが、コンポーネントのサービス内容の曖昧さ回避するために義務付けられていることもよくあります。リスト化されたサービスは、多くの場合インターフェースですが、サービスに具体的なクラスを使用することもできます。

@Componentはいつ使うか?
OSGiコンテナに公開する必要があるコンポーネントを作成するときに利用します。クラスのすべてがコンポーネントである必要はありません。コードをLiferay環境へプラグインする場合や(製品ナビゲーションメニューを追加する、MVCコマンドハンドラを定義する、Liferayコンポーネントをオーバーライドする時など)、独自の拡張フレームワークへプラグインする必要があるときにコンポーネントを宣言します。

@Reference

 


@Referenceは@Componentアノテーションに対応するものです。
@Referenceは、OSGiがコンポーネント参照をコンポーネントへ注入する際に使用し、OSGi @Componentクラスでのみ動作します。@Referenceアノテーションは、非コンポーネントやサブクラスでは無視されます。必要な参照の注入は、まず@Componentクラス自体で行われている必要があります。

これは、注入された多数のサービスにおいて基本クラスを定義したいときに役立ちます。基本クラスは@Componentアノテーションを取得せず、@Referenceアノテーションはコンポーネント以外のクラスでは無視されるため、注入は行われません。またコンポーネント以外のクラスでも@Referenceアノテーションは無視されるため、注入されません。すべてのsetterと、すべての具体的サブクラスへの@Referenceアノテーションをコピーすることになります。手間はありますが、必要な作業になります。

おそらく最もよく使われている属性は、”unbind”属性です。setterメソッドで@Reference(unbind = " - ")の形で検索されることがよくあります。 @Referenceでsetterメソッドを使用すると、OSGiは使用するコンポーネントとともにsetterを呼び出しますが、unbind属性はコンポーネントがバインドされていないときに呼び出すメソッドがないことを示します。したがって、基本的には扱う必要のないコンポーネントです。ほとんどの場合、サーバーが起動しすれば、OSGiがコンポーネントをバインドし、システムがシャットダウンするまでうまく使用することができるはずです。

ここで見られる別の属性は”target”属性です。Targetはフィルタメカニズムとして使用されます。
@Componentで扱うプロパティを覚えていますか? target属性では、受け取るコンポーネントのインスタンスを、より具体的に識別するクエリを指定できます。

例:

@Reference(
  target = "(javax.portlet.name=" + NotificationsPortletKeys.NOTIFICATIONS + ")",
  unbind = "-"
)
protected void setPanelApp(PanelApp panelApp) {
  _panelApp = panelApp;
}

 

上記は、PanelAppコンポーネントのインスタンスを取得したいときのコードで、特に、通知ポートレットに関連付けられたPanelAppコンポーネントを探しています。他のPanelAppコンポーネントはこのフィルタと一致せず、適用されません。

他の重要な属性としては、Cardinality属性があります。デフォルト値はReferenceCardinality.MANDITORYですが、その他の値は、OPTIONAL, MULTIPL, およびAT_LEAST_ONEです。これらの意味は以下の通りです:

  • MANDITORY - コンポーネントを開始する前に、参照を可能にし、注入する必要がある。
  • OPTIONAL - コンポーネントが開始するための参照は必要ではなく、コンポーネント割り当てがなくても機能する
  • MULTIPLE - 複数のリソースが参照を満たしている可能性があり、コンポーネントはそれらをすべて使用するが、OPTIONAL同様コンポーネントを開始するための参照は不要。
  • AT_LEAST_ONE - 複数のリソースが参照を満たしている可能性があり、コンポーネントはそれらをすべて使用するが、コンポーネントが開始するためには、少なくとも1つは確実に参照可能であること。

複数のオプションを使用すると、一致する参照を含む複数の呼び出しを取得できます。これは、Setterメソッドで@Referenceアノテーションを使用していて、メソッド内でリストまたは配列に追加しようとしていた場合にのみ意味があります。また、このような処理の代わりにServiceTrackerを使用すると、自分でコンポーネントのリストを管理する必要がなくなります。

Optionalを使用すると、割り当てられた参照なしにコンポーネントを起動することができます。これは次のような循環参照の問題があるシナリオの場合に役立ちます: Aを参照するCを参照するBをAが参照する場合。この3つすべてにREQUIREDを使用する場合、参照条件が満たされないため開始されません(開始コンポーネントのみ参照として割り当てられる)。 1つのコンポーネントで参照をoptionalとして扱うことでこの循環を分割することができます。これによって開始することができ、参照は解決されます。

次に重要な@Reference属性は”policy”です。 Policyは、ReferencePolicy.STATIC(デフォルト)またはReferencePolicy.DYNAMICのいずれかです。これらの意味は以下の通りです:

  • STATIC - コンポーネントは、割り当てられた参照がある場合にのみ起動され、代替サービスが利用可能になると通知される。
  • DYNAMIC - 参照がある場合もそうでない場合でもコンポーネントが開始され、新しい参照が利用可能になった場合もコンポーネントは受け入れる。

参照ポリシーは、新しい参照オプションが利用可能になったときに、コンポーネントが起動後の処理を制御します。
STATICの場合、新しい参照オプションは無視され、DYNAMICの場合コンポーネントが処理を変更します。

policyに加えて、もう一つの重要な@Reference属性は”policyOption”です。この属性は、ReferencePolicyOption.RELUCTANT(デフォルト)またはReferencePolicyOption.GREEDYのいずれかです。これらの意味は以下の通りです:

  • RELUCTANC - 単一参照Cardinalityの場合、使用可能になった新しい参照コンポーネントを無視する。複数の参照Cardinalityが設定されている場合、新しい参照可能コンポーネントがバインドされます。
  • GREEDY - 新しい参照コンポーネントが利用可能になった場合に、それらをバインドする。

選択肢が多くて混乱するかとおもいます...。
以下では、よく使われているグループ単位で説明します。

最初は、デフォルトのReferenceCardinality.MANDITORY、ReferencePolicy.STATICおよびReferencePolicyOption.RELUCTANTです。これは、コンポーネントが開始する参照サービスが1つだけでなければならないよう制御し、コンポーネントは新しいサービスが開始されても、これを無視します。これは優秀で正常なデフォルト設定であり、コンポーネントの安定性を確保します。

Liferayソースで一般的に使用されている別のグループは、ReferenceCardinality.OPTIONALまたはMULTIPLE、ReferencePolicy.DYNAMIC、およびReferencePolicyOption.GREEDYです。この構成では、コンポーネントは参照サービスの有無にかかわらず機能しますが、そのコンポーネントはその参照を変更/追加することを可能にし、新しい参照が利用可能となった時にそれらをバインドします。

他の組み合わせも可能ではありますが、コンポーネントへの影響を理解しておく必要があります。参照を宣言するということは、コンポーネントを完成させるために何らかのサービスが必要であると宣言しているのと同じことです。サービスが存在しないとき、そのコンポーネントはどう反応するのか、依存するサービスが利用できないためにコンポーネントが停止した場合にどうなるかなどを考慮してください。完璧な世界のシナリオだけでなく、再デプロイ、アンインストール、サービスギャップといった不具合を想定し、コンポーネントがそうした複雑な混乱をどのように乗り切ることができるかを確認します。様々な不具合乗り越えることに確信が持てれば、もう安心でしょう。

最後に、@Referenceアノテーションはいつ使うかですが、OSGi環境からコンポーネントへサービス注入が必要な場合に使います。注入は、自分のモジュールまたはOSGiコンテナ内の他のモジュールから行うことができます。 @ReferenceはOSGiコンポーネントでのみ機能しますが、@Componentリファレンスを追加することでコンポーネントへ変更できます。

@BeanReference

 


@BeanReferenceは、LiferayコアからSpring Beanへの参照を注入するために使用されるLiferayアノテーションです。

@ServiceReference


@ServiceReferenceは、Spring ExtenderモジュールBeanからの参照を注入するために使用されるLiferayアノテーションです。

3つの異なる参照アノテーション、どれを使うべきか?

 


3種類の参照アノテーションがあります。経験則からいうと、ほとんどの場合、@Referenceアノテーションだけを使います。 LiferayコアのSpring BeanとSpring ExtenderモジュールBeanも、OSGiコンポーネントとして公開されているため、@Referenceでほとんどの場合動作します。

@Referenceが注入されていない、またはnullの場合、他の参照アノテーションを1つ使用すべきであるというサインです。BeanがLiferayコアのBeanの場合は@BeanReferenceを使用し、Spring Extenderモジュールの場合は、@ServiceReferenceアノテーションを使用してください。Beanとサービスアノテーションの両方において、コンポーネントがSpring Extenderを使用している必要があることに注意してください。これを設定するには、ServiceBuilderサービスモジュールを調べて、build.gradleやbnd.bndファイルの更新方法などを確認してください。

@Activate


@Activateアノテーションは、OSGiにおけるSpringのInitializingBeanインタフェースに相当します。コンポーネントの起動後に、呼び出されるメソッドを宣言します。

Liferayソースでは、以下のような3つの主要なメソッドシグネチャが使用されています。

@Activate
protected void activate() {
  ...
}
@Activate
protected void activate(Map properties) {
  ...
}
@Activate
protected void activate(BundleContext bundleContext, Map properties) {
  ...
}

他にもメソッドシグネチャがあり、Liferayソースで@Activateを検索すればすべてのバリエーションを見ることができます。引数なしのactivateメソッドを除いて、それらはすべてOSGiによって注入された値に依存します。プロパティマップは実際にはOSGiのConfiguration Adminサービスのプロパティです。@Activeアノテーションはいつ使うかですが、 コンポーネントの起動後、もしくは利用前に初期化を完了する必要があるときはいつでも使います。私はQuartzジョブの設定やスケジューリング、データベースエンティティの検証などに使用しています。

@Deactivate


@Deactivateアノテーションは、@Activateアノテーションの逆であり、コンポーネントが非アクティブ化されているときに呼び出されるメソッドを識別します。

@Modified


@Modifiedアノテーションは、コンポーネントが変更されたときに呼び出されるメソッドを識別します。通常、@Reference(s)が変更されたことを示します。 Liferayコードでは、@Modifiedアノテーションは多くの場合@Activateアノテーションと同じメソッドにバインドされているため、同じメソッドのアクティブ化と変更の両方を処理します。

@ProviderType


@ProviderTypeはBNDに由来するのですが、複雑で扱いずらいと感じる方も多いのではないでしょうか。簡単な説明をすると、@ProviderTypeは、実装者のOSGiマニフェストに割り当てられたバージョン範囲を定義するためにBNDによって使用され、範囲を狭いバージョンの違いに制限しようとします。

これによって、インタフェースが変更されたときに、それより狭いバージョン範囲の実装があった場合に、実装のアップデートを強いることで、新しいインタフェースのバージョンと一致させた実装が、必ずシステムに配置されていることを保証することができます。

@ProviderTypeはいつ使うかですが、実をいうと、必要ではありません。このアノテーションは、ServiceBuilderで生成されたコード全体に散らばって表示されます。これは、実装者が使う必要があるからではなく、それらを見てなぜそこにあるのかと疑問に思うのを解消するためにあります。

@ImplementationClassName

@ImplementationClassNameは、ServiceBuilderエンティティインターフェイスのLiferayアノテーションです。インタフェースを実装するサービスモジュールのクラスを定義します。

これは、使用する必要があるインターフェイスではありませんが、少なくともなぜそこにあるのかを知っておくことが重要です。

@Transactional


@TransactionalはServiceBuilderサービスインターフェイスにバインドされた別のLiferayアノテーションです。これは、サービスメソッドのトランザクション要件を定義します。これも使用する必要はないアノテーションのひとつつです。

@Indexable

@Indexableアノテーションは、通常、エンティティを追加、更新、または削除するServiceBuilderメソッドに関連付けられ、インデックスを更新するメソッドを装飾するために使用されます。

@Indexableアノテーションは、インデックスエンティティの追加、更新、または削除するサービス実装メソッドで使用します。エンティティのcom.liferay.portal.kernel.search.Indexer実装が関連付けられている場合、インデックスが作成されているかどうかがわかります。

@SystemEvent


@SystemEventアノテーションは、ServiceBuilder生成コードに関連付けられているため、システムイベントが発生する可能性があります。システムイベントは、ステージングおよびLARエクスポート/インポートプロセスと連携して動作します。たとえば、記事が削除されると、SystemEventレコードが生成されます。ステージング環境で、「本番へ公開する」処理が発生すると、delete SystemEventによって、本番の対応する記事も確実に削除されます。

@SystemEventはいつ使うか? 正直いって、私もよくわかっていません笑。 10年間のOSGi開発経験で、SystemEventレコードを生成したり、パブリケーションやLARプロセスを変更したりする必要はありませんでした。 もし、@SystemEventアノテーションを使用したり変更したりしなければならない状況を経験した方がいたら、ぜひユースケースについて聞いてみたいと思います。

@Meta


OSGiには、Configuration Adminの設定の詳細を定義するXMLベースのシステムがあります。 BNDプロジェクトの@Metaアノテーションにより、BNDは設定インタフェースで使用されるアノテーションに基づいて、ファイルを生成することができます

注意ポイント:@Metaアノテーションを使用するには、次の行をbnd.bndファイルに追加する必要があります。

-metatype: *

上記を追加しなければ、@MetaアノテーションはXML設定ファイルを生成するときに使用されません。

@Meta.OCD


@Meta.OCDは、設定詳細のコンテナである、「オブジェクトクラス定義」のためのアノテーションです。このアノテーションは、クラス定義のID、名前、およびローカライゼーションの詳細を提供するために、インターフェースレベルで使用されます。

@Meta.OCDは コンポーネントを設定するために、システム設定コントロールパネルにパネルを持つAdmin設定インターフェースを定義するときに使います。

@Meta.OCD属性には、ローカライズ設定が含まれています。これにより、リソースバンドルを使用して、構成名、フィールドレベルの詳細、および@ExtendedObjectClassDefinitionカテゴリをローカライズすることができます。

@Meta.AD


@Meta.ADは、構成要素の仕様を定義するための項目レベルのアノテーションである:「属性定義」のアノテーションです。このアノテーションは、ID、名前、説明、デフォルト値、およびフィールドのその他の詳細を提供するために使用されます。

@Meta.ADはシステム設定の設定パネルで、レンダリング方法を制御するフィールド定義の詳細を提供する際に使用します。

@ExtendedObjectClassDefinition


@ExtendedObjectClassDefinitionは、設定のカテゴリ(システム設定コントロールパネルのタブが設定される場所を識別するため)と、設定の範囲を定義するためのLiferayアノテーションです。

意味は次のいずれかになります。

  • SYSTEM - システム全体のグローバル構成。システム全体を共有する構成インスタンスは1つのみ。
  • COMPANY - ポータル内のカンパニーIDごとに1つの設定インスタンスを許可する、企業レベルの設定。
  • GROUP - サイト毎レベルの構成インスタンスを許可する、グループレベル(サイト)構成。
  • PORTLET_INSTANCE - ポートレット・インスタンス設定のスコープと似ており、ポートレット・インスタンスごとに個別の構成インスタンスが存在する。

ExtendedObjectClassDefinitionは@ Meta.OCDアノテーションを使用する際は毎回、@ExtendedObjectClassDefinitionアノテーションを使用し、構成が追加されるタブを定義します。

@OSGiBeanProperties


@OSGiBeanPropertiesは、OSGiコンポーネントとしてSpring Beanを登録するために使用される、Liferayアノテーションです。ServiceBuilderモジュールで頻繁に使用され、Spring BeanをOSGiコンテナに公開しています。一点、ServiceBuilderはまだSpring(およびSpringExtender)ベースであることを忘れないでください。このアノテーションは、これらのSpring BeanをOSGiコンポーネントとして公開します。

@OSGiBeanPropertiesはモジュール内でSpringを使用する場合や、Spring Extenderを使用している場合に、他のモジュールがBeanを使用できるようにSpring BeanをOSGiに公開したい場合に、このアノテーションを使います。

@OSGiBeanPropertiesアノテーションのコードは広範にjavadocされているため、ここでの説明は割愛しています。詳細を知りたい方はぜひこちらを確認してみてください(英語)→https://github.com/liferay/liferay-portal/blob/master/portal-kernel/src/com/liferay/portal/kernel/spring/osgi/OSGiBeanProperties.java

まとめ


いかがでしたでしょうか?

少し長くなってしまいましたが、私がLiferay 7 CE / Liferay DXPでこれまでに遭遇したすべてのアノテーションを紹介してみました。これらの詳細情報が、みなさんのLiferay開発に役立てば幸いです。 私が見逃しているアノテーション見つけたり、もっと詳細をしりたいものがあれば、いつでも聞いてください。

本記事のお問い合わせはこちらまで→[email protected]


■ これまで発信してきた日本語による技術コンテンツを、Qiitaでお読みいただけます。よろしければそちらの記事も合わせてご役立てください。
https://qiita.com/yasuflatland-lf

■ Doorkeeperのライフレイコミュニティメンバー大歓迎!
コーポレートブログの技術コンテンツ更新情報など定期的におとどけしています。
https://liferay.doorkeeper.jp/