Google
 
Web andore.com
Powered by SmartDoc

9. ソースレベルメタデータのサポート(Ver 1.2.7)

9.1 ソースレベルメタデータ

ソースレベルメタデータはプログラム要素への属性やアノテーションを追加したものであり、通常はクラスやメソッドである。

例えば、下記のように、あるクラスにメタデータを追加することができる。

/**
 * Normal comments
 * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
 */
public class PetStoreImpl implements PetStoreFacade, OrderService {

下記のように、メソッドにもメタデータを追加することができる。

/**
 * Normal comments
 * @@org.springframework.transaction.interceptor.RuleBasedTransactionAttribute()
 * @@org.springframework.transaction.interceptor.RollbackRuleAttribute(Exception.class)
 * @@org.springframework.transaction.interceptor.NoRollbackRuleAttribute("ServletException")
 */
public void echoException(Exception ex) throws Exception {
    ....
}

この例では、jakarta CommonsのAttributesの文法を用いている。

ソースレベルメタデータは(Java業界における)XDoclet、およびマイクロソフトの.Netプラットフォームのリリースで主流に躍り出た。ここでは、ソースレベル属性をトランザクションやプーリング、その他の振る舞いを制御するのに利用している。

このアプローチのもつ価値は、J2EEコミュニティで評価された。例えば、EJBで排他的に用いられた従来のXMLデプロイメントデスクリプタよりも冗長性が大幅に軽減されるのだ。 プログラムソースコードから何かしらを外部に切り出すことは望ましいことではあるが、重要な全体的な設定 -- とりわけトランザクション特性 -- はプログラムソースの一部だ。EJBの仕様で仮定されていることには反するが、(トランザクションタイムアウトのようなパラメータは変更されることはあっても)メソッドのトランザクション特性を変更するのはほとんど意味がない。

メタデータ属性は典型的にはアプリケーションクラスが求めるサービスについて記述するためにフレームワークのインフラで主に使われるが、メタデータ属性が実行時に問い合わせを受けられるようするべきものでもある。これはXDocletのようなソリューションとの主たる違いであり、その違いはEJB自動生成物のようなコードを生成する方法としてまず最初にメタデータを見るというものである。

この分野には数多くのソリューションがあり、その中には下記のようなものがある。

9.2 Springにおけるメタデータのサポート

重要なテーマに関わる抽象的な概念の準備として、Springではメタデータ実装へのファサードがorg.springframework.metadata.Attributesインタフェースの形で提供されている。

このようなファサードに付加価値があるのはいくつかの理由がある。

SpringのAttributesインタフェースは下記のようなものである。

public interface Attributes {

    Collection getAttributes(Class targetClass);

    Collection getAttributes(Class targetClass, Class filter);

    Collection getAttributes(Method targetMethod);

    Collection getAttributes(Method targetMethod, Class filter);

    Collection getAttributes(Field targetField);

    Collection getAttributes(Field targetField, Class filter);
}

これは、必要最小限のインタフェースだ。JSR-175では、メソッド引数の属性のように、これより多くの機能が提示されている。Spring 1.0の時点では、Java 1.3以降のEJBや.NET風の効果的な宣言的エンタープライズサービスを提供するのに必要なメタデータのサブセットを提供することを目標とする。Spring 1.2の時点では、よく似たJSR-175のアノテーションはCommons Attributesと直接代わりになるのでJDK1.5でサポートされる。

このインタフェースは、.NETのようなObjectの属性を提示している点に注意してほしい。これは、(文字列属性しか提示しない)Nanning Aspectsと(DR2の時点での)JBoss 4のような属性システムとは異なるものだ。Object属性をサポートすることは十分な利点がある。属性によってクラス階層に組み入れることができ、また設定パラメータに賢く反応することができるようになる。

ほとんどのattributeの実装では、attributeクラスはコンストラクタ引数かJavaBeanプロパティを使って設定するCommons Attributesでは両方がサポートされている。

Springの抽象化APIが全部そうであるように、Attributesはインタフェースだ。これによりattributeの実装のモックテストやユニットテストが簡単にできるようになっている。

9.3 Jakarta Commons属性との統合

現状、SpringではJakarta CommonsのAttributesしか独自にはサポートしていないが、他のメタデータをサポートするためにorg.springframework.metadata.Attributesインタフェースの実装を用意するのは容易である。

CommonsのAttributes 2.1(http://jakarta.apache.org/commons/attributes/)は使いでのあるattributesのソリューションだ。コンストラクタ引数やjavaBeanプロパティからのattributeの設定をサポートしていて、attribute定義におけるよりよい自己ドキュメンテーションを提供する。(JavaBeanプロパティのサポートはSpringチームの要望により追加された)。

我々は、Commons Attributesのattribute定義についてこれまで2つの例を見てきたので、概説する必要があると思う。

ビーンプロパティは下記のようなものである。

/**
 * @@MyAttribute(myBooleanJavaBeanProperty=true)
 */

コンストラクタ引数とJavaBeanプロパティを(SpringのIoCの時のように)組み合わせることができる。

Java 1.5のAttributesとは異なり、Commons AttributesはJava言語に統合されていないので、ビルドプロセスの中で特別な属性コンパイルの手順を実行する必要がある。

To run Commons Attributes as part of the build process, you will need to do the following.

ビルドプロセスの中でCommons Attributesを実行するためには、下記の手順を踏む必要がある。

  1. 必要なライブラリのJarファイルを$ANT_HOME/libにコピーする。4つのライブラリが必須であり、これらは全てSpringで配布されているものだ。
  2. 下記のように、Commons Attributesアントタスクをプロジェクトのビルドスクリプトにインポートする
<taskdef resource="org/apache/commons/attributes/anttasks.properties"/>
  • 次に、属性コンパイルタスクを定義する。これは、ソースコード中の属性を"コンパイル"するために、Commons Attributesの属性コンパイルタスクを用いる。この処理の結果、新たにソースコードがdestdir属性で指定された位置に生成される。この例では、テンポラリディレクトリを用いている。
    <target name="compileAttributes">
    
      <attribute-compiler destdir="${commons.attributes.tempdir}">
        <fileset dir="${src.dir}" includes="**/*.java"/>
      </attribute-compiler>
    
    </target>
    

    このソース上でjavacを実行するコンパイル対象はこの属性コンパイルタスクに依存させるべきであり、出力先ディレクトリに生成されたソースもコンパイルしなければならない。もし、属性定義にシンタックスエラーがあると、通常は属性コンパイラで検出される。しかしながら、もし属性定義が文法上妥当ではあるが、無効な型やクラス名を指定していると、生成されたattributeクラスのコンパイルは失敗する。このような場合、生成されたクラスを見れば問題の原因をつかむことができる。

    CommonsのAttributesはMavenもサポートしている。詳しい情報については、Commons Attributesのドキュメントを参照してほしい。

    この属性コンパイルの手順は一見複雑に見えるが、実際は1回限りの手間でしかない。一旦設定してしまえば、属性コンパイル処理は追加されるので、通常はビルド処理が遅くなったと思うようなことはない。また一度コンパイル処理の設定をしてしまえば、本章で述べたようなattributesを使うことで他の部分で多くの手間を省くことができるということがわかると思う。

    もし属性インデックスのサポートが必要であれば(現状では、後述する、Springのattribute-targetedウェブコントローラでのみ必要となる)、コンパイルしたクラスのJarファイルに対してさらに別の手順が必要になる。この追加の手順では、Commons Attributesはソースコードに定義された全属性のインデックスを生成し、実行時のルックアップの効率化を行う。この手順は下記のようなものだ。

    <attribute-indexer jarFile="myCompiledSources.jar">
        
        <classpath refid="master-classpath"/>
    
    </attribute-indexer>
    

    このビルド手順の例として、SpringのjPetStoreサンプルアプリケーションの/attributesディレクトリを見てほしい。ここにビルドスクリプトがあるのでこれをプロジェクトに合わせて修正して欲しい。

    もし、あなたのユニットテストがattributesに依存しているのであれば、CommonsのAttributesを使うのではなく、SpringのAttributes抽象化を用いて依存関係を表現してみることをお勧めす。これによりよりポータブルになる、-- 例えば、もし将来Java1.5のattributesに差し替えても今作ったテストが動く -- だけでなく、テストを単純化できるのだ。Springでは、モックテストが簡単になるようなメタデータインタフェースを用意しているが、CommonsのAttributesはスタティックなAPIなのだ。

    9.4 メタデータとSpring AOPのオートプロキシ

    メタデータ属性の最も重要な用途はSpring AOPとの結合だ。これにより、.NETライクなプログラミングモデル、つまりメタデータ属性を宣言したアプリケーションオブジェクトに宣言的サービスが自動的に提供されるような仕組みが提供される。このようなメタデータ属性は、宣言的トランザクション管理の場合のように本フレームワークによる独創的なサポートがされているかあるいは自前のものだ。

    AOPとメタデータ属性の間には幅広いシナジー効果があるのだ。

    9.4.1 基本事項

    これは、Spring AOPのオートプロキシ機能により実現されている。設定はおそらくこんな感じになると思う。

    <bean \
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> \
    
    <bean \
        class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> \
      <property name="transactionInterceptor" ref="txInterceptor"/>
    </bean>
    
    <bean id="txInterceptor" \
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> \
      <property name="transactionManager" ref="transactionManager"/>
      <property name="transactionAttributeSource">
    <bean \
        class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> \
          <property name="attributes" ref="attributes"/>
        </bean>
      </property>
    </bean>
    
    <bean id="attributes" \
        class="org.springframework.metadata.commons.CommonsAttributes"/>
    

    この基本的な考え方は、AOPの章にあるオートプロキシに関する議論でよくご理解いただけていると思う。

    最も重要なビーン定義はautoproxytransactionAdvisorという名前のビーンのものだ。ここで注意して欲しいのは、実際のビーン名は重要ではない。そのクラスがどういったものかが重要なのだ。

    org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreatorクラスのビーン定義は、属するファクトリにある、アドバイザ実装にマッチした全てのインスタンスをアドバイス("オートプロキシ")する。このクラスは属性については何も知らないが、Advisorのポイントカットマッチングに従う。ポイントカットは属性のことをわかっているのだ。

    従って、属性に基づいた宣言的トランザクション管理を提供するAOPアドバイザが必要なだけである。

    任意にカスタマイズしたAdvisorの実装を追加することも同様に可能であり、自動的に評価、適用が行われる。(もし、必要であれば、同じオートプロキシ設定の別の属性の条件にポイントカットがマッチするAdvisorを使っても構わない。)

    最後に、このattributesビーンはCommons AttributesのAttributesの実装である。他のソースから属性を取得するために、org.springframework.metadata.Attributesのほかの実装に置き換えよう。

    9.4.2 宣言的トランザクション管理

    ソースレベル属性の最も一般的な使い方は、.NET風の宣言的トランザクション管理の提供だ。一旦、上述したビーン定義を配置すれば、宣言的トランザクションを必要とするアプリケーションオブジェクトをいくつでも定義することができる。トランザクション属性をもつクラスやメソッドだけにトランザクションアドバイスが渡されるようになる。必要トランザクション属性を定義する以外には、他に何もする必要はないのである。

    .NETとは違い、トランザクション属性をクラスやメソッドごとに指定することもできる。クラスレベル属性は、指定されると、全てのメソッドで"継承される"。メソッド属性はクラスレベル属性でオーバライドされる。

    9.4.3 プーリング

    繰り返しになるが、.NETでのように、クラスレベル属性によってプーリングの振る舞いを有効にすることができる。Springではこの振る舞いを任意のPOJOに適用することができる。pooling属性を以下のようにプール対象とするビジネスオブジェクトに設定するだけでよい。

    /** 
     * @@org.springframework.aop.framework.autoproxy.target.PoolingAttribute(10)
     * @author Rod Johnson
     */
    public class MyClass {
    

    通常はオートプロキシのインフラ設定を必要とする。そして、プールするTargetSourceCreatorを以下のように指定する必要がある。プーリングはターゲットの生成に影響を及ぼすので、通常のアドバイスは利用することができない。pooling属性があれば、そのクラスに適用可能なアドバイザが存在していなくてもプーリングは適用される、という点に注意して欲しい。

    <bean id="poolingTargetSourceCreator"
    class="org.springframework.aop.framework.autoproxy.metadata.AttributesPoolingTargetSourceCreator">
      <property name="attributes" ref="attributes"/>
    </bean>
    

    適切なオートプロキシビーン定義にはPooling target source creatorを含んだ"custom target source creators"のリストを指定する必要がある。上述した例を修正しこのプロパティを含むようにしたものを以下に示す。

    <bean \
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> \
      <property name="customTargetSourceCreators">
        <list>
          <ref bean="poolingTargetSourceCreator"/>
        </list>
      </property>
    </bean>
    

    一般に、Springでメタデータを使う場合のように、これは1回しか修正の手間がかからない。一度この設定をやってしまえば、追加したビジネスオブジェクトをプーリングするのはとても簡単だ。

    プーリングが必要になることはまれである、という点には議論の余地がある。つまり、多くのビジネスオブジェクトにプーリングを適用する必要性はめったにない。従って、この機能は通常はあまり日の目をみないと思う。

    より詳細な情報については、org.springframework.aop.framework.autoproxyパッケージのJavadocを参照してほしい。CommonsのPoolに独自のコードを最小限使うのではなく、他のプーリングの実装を使うことも可能だ。

    9.4.4 カスタムメタデータ

    オートプロキシの基盤には柔軟性が備わっているので、.NETメタデータ属性よりも可能性がある。

    ある種の宣言的振る舞いを実装するのに、独自の属性を定義することができる。これには以下の手順を必要とする。

    独自の宣言的セキュリティやキャッシュ機能のように潜在的に実施したいものがいくつかあると思う。

    これは、プロジェクトにおいて設定の手間を大きく削減することができる強力なメカニズムだ。しかしながら、この背景にAOPに依存している、という点については覚えておいてほしい。動作するAdvisorが増えれば増えるほど、実行時の設定はより複雑になってしまう。(もし、どのアドバイスがどのオブジェクトに適用されるのかを知りたければ、リファレンスをorg.springframework.aop.framework.Advisedにキャストしてみればいい。これによりAdvisorを調べることができると思う)。

    9.5 MVCウェブ層の設定を最小限にするために属性を使う

    1.0の時点でのSpringのメタデータのその他の主な用途は、Spring MVCウェブ設定を簡単にするオプションを提供することだ。

    Spring MVCでは、柔軟なハンドラのマッピング、つまり入力リクエストからコントローラ(あるいは他のハンドラ)インスタンスへのマッピングを提供する。通常、ハンドラマッピングは適切なSpring DispatcherServlet用にxxx-servlet.xmlファイルで設定されている。

    このマッピングをDispatcherServlet設定ファイル内に保持することは通常はいい考えだ。最大限の柔軟性を確保できる。特に下記の点が挙げられる。

    しかしながら、これは各コントローラごとに、典型的に必要になる両方のハンドラマッピング(通常はハンドラマッピングXMLビーン定義にある)とコントローラ自体のためのXMLマッピングの両方が必要になる、ということだ。

    Springではソースレベル属性に基づく単純なアプローチが用意されており、これは単純なシナリオにおいては魅力のある選択肢だと思う。

    本セクションで述べているこのアプローチは比較的単純なMVCシナリオに最も適している。これは、同じコントローラを別のマッピングで使うことできるとか、リクエストURLではない何かに基づくマッピングさせることができるといったSpringのMVCの能力をある程度制限する。

    この方法では、コントローラは、マッピングするURLを1つだけ指定したクラスレベルのメタデータ属性を1つもしくは複数マークされる。

    下記の例はこの方法を示したものだ。各々のケースで、Cruncher型のビジネスオブジェクトに依存するコントローラを用意した。通常は、この依存性は依存性注入により解決される。このCruncherは適切なDispatcherServlet XMLファイルもしくは親のコンテキストにあるビーン定義から利用可能でなければならない。

    ここで、マッピングすべきURLを指定したコントローラクラスに属性を追加する。依存性はJavaBeanプロパティかもしくはコンストラクタ引数を用いて表現することができる。この依存性は、オートワイヤで解決されなければならない。すなわち、確実に型Cruncherのビジネスオブジェクトが1つコンテキスト中に存在していなければならない。

    /**
     * Normal comments here
     * @author Rod Johnson
     * @@org.springframework.web.servlet.handler.metadata.PathMap("/bar.cgi")
     */
    public class BarController extends AbstractController {
    
        private Cruncher cruncher;
    
        public void setCruncher(Cruncher cruncher) {
            this.cruncher = cruncher;
        }
    
        protected ModelAndView handleRequestInternal(
                HttpServletRequest request, HttpServletResponse response)
                throws Exception {
            System.out.println("Bar Crunching c and d =" + 
                cruncher.concatenate("c", "d"));
            return new ModelAndView("test");
        }
    
    }
    

    この自動マッピングを有効にするには、下記を属性ハンドラマッピングを指定した適切なxxx-servlet.xmlファイルに追加する必要がある。この特別なハンドラマッピングは任意の数のコントローラと属性を上記のように扱うことができる。このビーンID("commonsAttributesHandlerMapping")は重要ではない。型が問題なのだ。

    <bean id="commonsAttributesHandlerMapping"      
    class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping"/>
    

    現状では、上述した例のようにAttributesビーン定義は必要とはしていない。というのは、このクラスはSpringのメタデータ抽象化経由ではなく、直接Commons Attributes APIで動作するからだ。

    今は、各コントローラごとのXML設定は必要ない。コントローラは自動的に指定されたURLにマッピングされる。コントローラはSpringのオートワイヤ機能を使い、IoCの恩恵を受ける。例えば、上述した単純なコントローラの"cruncher"というビーンプロパティで示された依存性は現状のウェブアプリケーションコンテキストで自動的に解決される。これは、セッターインジェクションとコンストラクタインジェクションのどちらも解決可能であり、特別な設定はひとつも必要ない。

    複数のURLパスのコンストラクタインジェクションの例を下記に示す。

    /**
    * Normal comments here
    * @author Rod Johnson
    * 
    * @@org.springframework.web.servlet.handler.metadata.PathMap("/foo.cgi")
    * @@org.springframework.web.servlet.handler.metadata.PathMap("/baz.cgi")
    */
    public class FooController extends AbstractController {
    
        private Cruncher cruncher;
    
        public FooController(Cruncher cruncher) {
            this.cruncher = cruncher;
        }
    
        protected ModelAndView handleRequestInternal(
                HttpServletRequest request, HttpServletResponse response)
                throws Exception {
            return new ModelAndView("test");
        }
    
    }
    

    この方法は、下記のような利点がある。

    この方法では以下のような制限がある。

    型によるオートワイヤリングでは、指定された型の依存性は1つのみが存在していないといけないので、AOPを使う場合は注意を払う必要がある。例えば、transactionProxyFactoryBeanを使う場合、Cruncherのようにビジネスインタフェースの実装を2つ、オリジナルのPOJOの定義と、トランザクションAOPプロキシになってしまう。これでは、持っているアプリケーションコンテキストが型の依存性を明確に解決できないので、動作しない。これを解決するには、定義されたCruncherの実装が1つしかないので、オートプロキシ基盤を設定して、AOPオートプロキシを使うことだ。すると、この実装を自動的にアドバイスされる。なので、この方法であれば上述したような属性をターゲットとする宣言的サービスでも動作する。属性コンパイル手順は対象となるウェブコントローラを処理のに決まった手順でなければいけないので、これをセットアップするのは簡単だ。

    他のメタデータ機能とは異なり、現状はCommons Attributesの実装,org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlermappingだけしか利用することができない。この制限は、PathMap属性をもつクラス全部でattributesのAPIを叩けるようにするために属性コンパイラだけでなく属性インデックシングも必要である、という事実によるものだ。インデクシングは現状では、org.springframework.metadata.Attributes抽象インタフェースでは用意されていないが、ゆくゆくは用意されると思う。(他の属性の実装のサポートを追加したい、--これは、インデクシングをサポートしないといけない--という場合は、CommonsPathMapHanderMappingのスーパクラス、AbstractPathMapHandlerMappingを拡張し、2つのprotectedな抽象メソッドを実装して、求める属性用のAPIを実装すれば簡単にできる)。

    従って、新たにビルド手順に2つの処理を追加する必要がある。属性コンパイルと属性インデクシングだ。属性インデクサタスクを使うのは上述した。Commons Attributesでは現状、インデクシングの入力に対し、jarファイルを必要とすることに注意してほしい。

    メタデータマッピング方式のハンドラを使うのであば、いつでもSpringのこれまでのXMLマッピング方式に切り替えることができるので、この選択肢に限定されることはない。このため、私はしばしばウェブアプリケーションをメタデータマッピングを使って作り始めることもある。

    9.6 メタデータ属性の他の使い方

    メタデータ属性のほかの用途については、ポピュラーになりつつあるように見える。Spring 1.2の時点で、(JDK 1.3以降で)Common Attributes、(JDK 1.5で)JSR-175のアノテーションにより、JMXアクセス用メタデータ属性がサポートされている。

    9.7追加したメタデータAPIのサポートを追加する

    他のメタデータAPIをサポートしたいと思うのであれば、簡単にできるのでやるべきだ。

    単に、org.springframework.metadata.AttributesインタフェースをあなたのメタデータAPIに対するファサードとして実装するだけだ。そうすると、上述したようにこのオブジェクトを自分のビーン定義に含めることができるようになる。

    メタデータドリブンなオートプロキシのような、メタデータを用いているフレームワークサービスは全て自動的にあなたの新しいメタデータプロバイダに使うことができるようになる。