連載
» 2005年07月16日 00時00分 公開

Seaser Projectの全貌を探る(2):DI+AOPを実現するSeasarV2 (2/3)

[沖林正紀,@IT]

DIの作成例

 それでは実際の作成例を見てみましょう。ここでは2次元の図形の面積を計算するアプリケーションを、それぞれのDIの方法に合わせて3種類作成しました。あらかじめ、どのアプリケーションでも共通する、面積を求めるメソッドをインターフェイスとして作成してあります(リスト1)。

リスト1 面積を求めるメソッドを持つインターフェイス(アプリケーション共通) [Area.java]
package myfirst.di;

// コンポーネントに共通するインターフェース
public interface Area  {
  double findArea();  // 面積を求めるメソッド
}

コンストラクタ・インジェクションの作成例

 最初にコンストラクタ・インジェクションの作成例からです。リスト2は三角形の面積を求めるJavaプログラムで、リスト1のインターフェイスを実装しています。面積を求めるには、底辺と高さの値が必要です。そしてこのプログラムでは図形の名称も登録できるようにしてあります。これらの値はコンストラクタで設定できるようになっています。

リスト2より抜粋
// コンストラクタ・インジェクション
public TriangleArea( String n, double b, double h ) {
  name = n; // 図形の名称
  base = b; // 底辺
  height = h; // 高さ
}

 底辺や高さの実際の値は、diconファイル内に、以下のようなコンポーネント定義として記述しておきます。そうするとS2のDIコンテナからリスト2のインスタンスを取得する際に、上記のコンストラクタを用いてリスト2に値が設定されます。

diconファイルより抜粋
<component name="triangle" class="myfirst.di.TriangleArea">
  <arg>"三角形A"</arg><!-- 図形の名称(文字列の定数は二重引用符で囲む) -->
  <arg>5.0</arg><!-- 底辺 -->
  <arg>7.0</arg><!-- 高さ -->
</component>

 <component>〜</component>は、コンテナから取得するオブジェクトを表します。class属性にクラスのフルパッケージ名を記述します。<arg>〜</arg>がコンストラクタに設定する引数の値を表しますが、このとき記述する引数の順はソースコードに記述された引数の順に合わせる必要があります。リスト2はコンストラクタの引数が図形の名称、底辺、高さの順ですので、diconファイルの方もそれに合わせてあります。

注意 diconファイルで文字列の値を記述する際の注意

文字列の値(String型の定数)を記述する際には、必ず定数の両端を二重引用符(")で囲まなくてはなりません。二重引用符で囲まれていない文字列が記述されていた場合、diconファイルでは<component>タグのname属性の値が指定されたものとして認識されます。


リスト2 三角形の面積を求めるJavaプログラム [TriangleArea.java]
package myfirst.di;

// 三角形の面積を求めるクラス
public class TriangleArea implements Area  {

  private String name;          // 図形の名称
  private double base, height;  // 底辺・高さ

  // コンストラクタ・インジェクション
  public TriangleArea( String n, double b, double h )  {
    name   = n;   // 図形の名称
    base   = b;   // 底辺
    height = h;   // 高さ
  }

  // 面積(底辺×高さ÷2) - Areaインターフェースの実装
  public double findArea()  {  return base * height / 2.0;  }

  // 計算結果の表示(cm^2は平方センチメートルを表す)
  public String toString()  {
    return
      String.format( "底辺%5.2fcm, 高さ%5.2fcm の%sの面積は %5.2fcm^2 です。",
                     base, height, name, findArea() );
  }

}

セッター・インジェクションの作成例

 セッター・インジェクションは、値の設定をsetter(セッター)メソッドを使って行うものです。今度は円の面積を求めるJavaプログラム(リスト3)を例に示します。円の面積を求めるためには半径と円周率の値が必要です。setterメソッドは以下のように作成しました。

リスト3より抜粋
// セッター・インジェクション
public void setName ( String n ) { name = n; } // 図形の名称
public void setRadius( double r ) { radius = r; } // 半径
public void setPi ( double p ) { pi = p; } // 円周率

 setName、setRadius、setPiという、それぞれのsetterメソッドの存在によって、コンテナでは、リスト3のインスタンスがname、radius、piというプロパティを持っていると認識されます。リスト3の内部でどのような名前の変数が用いられているかは関係ありません。

 diconファイルでの設定は以下のようになります。<component>〜</component>間は先ほどと同じですが、セッター・インジェクションの場合は<property>〜</property>を用いて値を設定します。このタグのname属性はsetterメソッドで示されたプロパティ名を記述します。例えば<property name="name">〜</property>には、setNameメソッドで設定する値を記述します。ですから、プロパティの名称さえ間違えていなければ、diconファイルで記述される順とJavaソースコードで記述される順とが異なっていても適切に処理されます。

diconファイルより抜粋
<!-- セッター・インジェクション(メソッド名は"setプロパティ名"とする) -->
<component name="circle" class="myfirst.di.CircleArea">
  <property name="name">"円B"</property><!-- 図形の名称 -->
  <property name="radius">5.0</property><!-- 半径 -->
  <property name="pi">3.14159</property><!-- 円周率(π) -->
</component>

リスト3 円の面積を求めるJavaプログラム [CircleArea.java]
package myfirst.di;

// 円の面積を求めるクラス
public class CircleArea implements Area  {

  private String name;        // 図形の名称
  private double radius, pi;  // 半径、円周率(π)の値

  // セッター・インジェクション
  public void setName  ( String n )  {  name   = n;  }  // 図形の名称
  public void setRadius( double r )  {  radius = r;  }  // 半径
  public void setPi    ( double p )  {  pi     = p;  }  // 円周率

  // 面積(半径×半径×円周率) - Areaインターフェースの実装
  public double findArea()  {  return radius * radius * pi;  }

  // 計算結果の表示(cm^2は平方センチメートルを表す)
  public String toString()  {
    return
      String.format( "円周率を %7.5f としたとき、半径%5.2fcm の%sの面積は %5.2fcm^2 です。",
                     pi, radius, name, findArea() );
  }

}

メソッド・インジェクションの作成例

 S2においてDIを行う方法の最後はメソッド・インジェクションです。これはsetter以外のメソッドで値を設定したいときに用いる方法です。通常は先述した2つの方法で事足りるのですが、何らかの事情でそれ以外のメソッドによるDIが必要になる場合に用います。

 値の設定に用いているメソッドは、長方形の面積を求めるJavaプログラム(リスト4)のsetName、 setBaseAndHeightの2つです。これらのメソッドでは、図形の名称と面積の計算に必要な底辺と高さを設定できるようにしてあります。

リスト4より抜粋
// メソッド・インジェクション(OGNL式で指定)
public void setName( String n ) { name = n; }

// メソッド・インジェクション(メソッド名で指定)
public void setBaseAndHeight( double b, double h ) {
  base = b; // 底辺
  height = h; // 高さ
}

 diconファイルでの記述は、以下のように<initMethod>〜</initMethod>を用います。このとき、name属性でメソッド名を記述してもよいですし、OGNL(http://www.ognl.org/)式で直接メソッドの実行を指示することもできます。#selfはOGNL式でそのクラス自身を表します。

 OGNL(Object-Graph Navigation Language)は式言語の一種ですが、JSPのそれとは記述方法が異なります。詳細はSeasarプロジェクトのOGNLガイド(http://www.seasar.org/ognl.html)を参照してください。ちなみに、本稿で用いているS2.2.10ではOGNLバージョン2.6.5が用いられています。

 メソッドの引数はコンストラクタ・インジェクションの場合と同様に<arg>〜</arg>で表します。引数を記述する順はやはりJavaソースコードと合わせておかなくてはなりません。リスト4で底辺と高さの値を設定するsetBaseAndHeightメソッドの場合は第1引数が底辺、第2引数が高さですので、diconファイルでも先に底辺、次に高さの値を記述しています。

diconファイルより抜粋
<!-- メソッド・インジェクション(メソッド名やOGNL式で初期化メソッドを指定) -->
<component name="rectangle" class="myfirst.di.RectangleArea">
  <initMethod>#self.setName("長方形C")</initMethod><!-- OGNL式で指定 -->
  <initMethod name="setBaseAndHeight"><!-- メソッド名で指定(引数の順はソースコードに合わせる) -->
    <arg>5.0</arg><!-- 底辺 -->
    <arg>7.0</arg><!-- 高さ -->
  </initMethod>
</component>

リスト4 長方形の面積を求めるJavaプログラム [RectangleArea.java]
package myfirst.di;

// 長方形の面積を求めるクラス
public class RectangleArea implements Area  {

  private String name;          // 図形の名称
  private double base, height;  // 底辺・高さ

  // メソッド・インジェクション(OGNL式で指定)
  public void setName( String n )  {  name = n;  }

  // メソッド・インジェクション(メソッド名で指定)
  public void setBaseAndHeight( double b, double h )  {
    base   = b;   // 底辺
    height = h;   // 高さ
  }

  // 面積(底辺×高さ) - Areaインターフェースの実装
  public double findArea()  {  return base * height;  }

  // 計算結果の表示(cm^2は平方センチメートルを表す)
  public String toString()  {
    return
      String.format( "縦%5.2fcm, 横%5.2fcm の%sの面積は %5.2fcm^2 です。",
                     base, height, name, findArea() );
  }
}

コンテナによるインスタンスの生成(プログラムの実行)

 ここまでで3つのDIの方法を紹介してきましたが、それだけではリスト24を実行することはできません。これらのプログラムを実行するには、DIコンテナでインスタンスを生成してもらう必要があります。例えばリスト2を実行するには、以下のようにします。

// diconファイルのパスを設定
String diconFile = ...... ;
// S2コンテナオブジェクトを生成
S2Container container = S2ContainerFactory.create( diconFile );
// リスト2のインスタンスを取得
TriangleArea ta = (TriangleArea)container.getComponent( "triangle" );
// 面積の計算結果を表示
System.out.println( ta );

 リスト2のインスタンスを生成するのはS2Containerクラスですが、これ自体のインスタンスはS2ContainerFactoryクラスのcreateメソッドで生成してもらいます。このとき、引数としてdiconファイルのパスが必要です。

 こうして生成されたS2Containerインスタンスを用いて、diconファイルによる定義を加味したリスト2のインスタンスを生成します。このとき、同じインターフェイスを持つクラスがほかになければ、getComponent( Area.class )というように、引数に直接インターフェイスを設定しても良いのですが、リスト2〜4に関する定義をすべて同じdiconファイルに記述してしまうと、S2Containerがどのコンポーネントを用いればよいか分からず、エラーとなってしまいます。そういう場合には、<component>タグのname属性に記述した名称で、インスタンスを生成してほしいクラスを指定します。

 diconファイルの記述も、先述の分だけではなく、複数のコンポーネント定義を<components>〜</components>タグで囲み、xml宣言やDOCTYPE宣言を追加して、全体で1つのXMLデータとなるようにします。結果としてdiconファイルの全体像は以下のようになります。

 <include>タグは、別のdiconファイルに記述してある定義を読み込んで利用するためのタグです。データベース接続を行う場合など、同じ設定を複数のdiconファイルで共有する場合に用いられます。

diconファイルの全体像(S2.2.xxの場合) [figurearea.dicon]
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
                            "http://www.seasar.org/dtd/components.dtd">
<components>
  <include path="... インクルードするdiconファイルのパス ..." />
  <component name="... コンポーネントの名称 ..." class="... クラス名 ...">
    <!-- 各種の設定 -->
  </component>
  <component>
    <!-- ほかのコンポーネントの定義 -->
  </component>
  <!-- 繰り返し -->
</components>

 この項の最後に、リスト24までを実行するプログラム(リスト5)とその実行結果を掲載します。ちなみに実行結果に表示されている“cm^2”は平方センチメートルを表します。

リスト5の実行結果

>java -cp .;%CLASSPATH% myfirst.di.FigureArea figurearea.dicon
底辺 5.00cm、 高さ 7.00cm の三角形Aの面積は 17.50cm^2 です。
円周率を 3.14159 としたとき、半径 5.00cm の円Bの面積は 78.54cm^2 です。
縦 5.00cm, 横 7.00cm の長方形Cの面積は 35.00cm^2 です。


リスト5 リスト2〜4を実行するJavaプログラム [FigureArea.java]
package myfirst.di;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class FigureArea  {  // 図形の面積を求める

  private void start( String diconFile )  {  // 引数はdiconファイル名

    // S2コンテナオブジェクトを生成
    S2Container container = S2ContainerFactory.create( diconFile );

    // 図形のオブジェクトを取得
    // -- Areaインターフェースを実装したオブジェクトが1つだけの場合は
    // -- (Area)container.getComponent( Area.class )のように記述してもよい
    TriangleArea  ta = (TriangleArea) container.getComponent( "triangle"  );
    CircleArea    ca = (CircleArea)   container.getComponent( "circle"    );
    RectangleArea ra = (RectangleArea)container.getComponent( "rectangle" );

    // 面積の計算結果を表示
    System.out.println( ta + "\n" + ca + "\n" + ra );

  }

  public static void main( String[] args )  {
    if ( args.length < 1 )  {
      System.out.println( "java FigureArea diconファイル名" );
      System.exit( 1 );
    }
    FigureArea fa = new FigureArea();
    fa.start( args[ 0 ] );
  }

}

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。