第4回 疑似EJB 3.0環境をSpringとXDocletで作る



西ヶ谷岳(サン・マイクロシステムズ)
2005/10/20

  Page.1 Page.2

 第1回の「JSF・Spring・Hibernateで次世代Javaに備える」で述べたように、EJB 3.0ではビジネス層のサービス・オブジェクトはPOJOとして実現できます。また、AOPの機能が利用可能となり、ビジネスロジックに手を加えることなく、特定の前処理や後処理をビジネスロジックに織り込むことができるインターセプターを利用することができるようになります。

 今回は、上記のようなEJB 3.0と同等の環境を構築します。そんなことが簡単にできるだろうか、と疑問に思われる方もいるかもしれません。しかし、SpringフレームワークやXDocletを応用することで、それほど工数を掛けずにEJB 3.0にかなり近い開発環境を用意することができるのです。

   サービス・オブジェクトをPOJOでデザインする


 ここでの目標は、サービス・オブジェクトをPOJOとして実装できるようにすることです。すなわち、以下の例のように、特定の環境やライブラリに依存せず、階層化アーキテクチャに基づいて分離したほかのレイヤのコンポーネントとの依存関係は、それらのインターフェイス定義のみによって依存するようにサービス・オブジェクトを定義します。

リスト1 CustomerServiceインターフェイス
public interface CustomerService {

    public List findCustomer(CustomerCriteria criteria);
    
    public List deleteCustomer(int id, CustomerCriteria criteria);
}

リスト2 CustomerServiceの実装クラス
public class CustomerServiceImpl implements CustomerService {
    
    public List findCustomer(CustomerCriteria criteria) {
        return customerDAO.find(criteria.getName(),
                                criteria.getCompany());
    }
    
    public List deleteCustomer(int id, CustomerCriteria criteria) {
        customerDAO.delete(id);
        return findCustomer(criteria);
    }

    private CustomerDAO customerDAO;
    
    public void setCustomerDAO(CustomerDAO customerDAO) {
        this.customerDAO = customerDAO;
    }    
}

 顧客情報の検索や削除の実際の処理は、インテグレーション層のデータ・アクセス・オブジェクトCustomerDAOに委譲するため、CustomerDAOをCustomerServiceにインジェクションするためのsetterメソッドも用意しています。Java EE 5環境が利用可能になれば、CustomerDAOのインジェクションには以下のような@Resourceアノテーションによってワイヤリングできるでしょう。

@Resource(name="CustomerDAO")
    public void setCustomerDAO(CustomerDAO customerDAO) {
        this.customerDAO = customerDAO;
    }    

 J2EE 1.4ベースの現状では、アノテーションの代わりに、SpringフレームワークのXMLメタデータを利用したワイヤリングで代用します。

<bean name="CustomerService"
        class="com.example.business.CustomerServiceImpl">
    <property name="customerDAO">
      <ref bean="CustomerDAO"/>
    </property>
</bean> 


   POJOのサービスオブジェクトをEJBでラップする

 ビジネス・オブジェクトにEJBを利用するメリットは、第2回「鍵はPOJOベースのアプリケーション・デザイン」にも述べたとおりJ2EE 1.4標準技術で、トランザクション管理やロールベースのアクセス制御が実現できるからです。そして、EJBを利用したビジネスロジックには、トランザクションやアクセス制御のコードは一切記述する必要がない環境がすでにあるわけです。

 しかし、EJBは特定のインターフェイス(すなわち、SessionBeanインターフェイス)を実装し、決められたルールに従ってメソッドを実装しなければいけません。制約の少ないPOJOベースデザインとEJBの優れた機能の両方のメリットを得るためには、サービス・オブジェクトのビジネス・メソッドをデレゲート呼び出しするセッション・ビーンを用意すればよいことになります。

リスト3 CustomerServiceSessionBean.java
import org.springframework.ejb.support.AbstractStatelessSessionBean;
import javax.ejb.CreateException;

/**
 * @ejb.bean
 *        name="CustomerService"
 *        view-type="both"
 *        type="Stateless"
 *        jndi-name="ejb/CustomerService"
 *            :
 */
public class CustomerServiceSessionBean
    extends AbstractStatelessSessionBean {

    /** POJOのサービス・オブジェクト */
    private CustomerService service;

    protected void onEjbCreate() throws CreateException {
        // BeanFactoryからPOJOのサービス・オブジェクトを取得する
        service = (CustomerService) getBeanFactory().getBean(
            "CustomerService");
    }

    /**
     * @ejb.transaction type="Required"
     * @ejb.interface-method
     */
    public java.util.List findCustomer(CustomerCriteria criteria)  {
        // POJOのビジネス・メソッドをデレゲート呼出しする
        return service.findCustomer(criteria);
    }
             :
}

 すなわち、POJOのビジネスメソッドと全く同じシグネチャのメソッドを用意し、セッション・ビーンのビジネスメソッドが呼び出されたら、POJOのビジネスメソッドを単に呼び直せばよいのです。後は、セッション・ビーンにどのようにして、POJOのサービス・オブジェクトの参照を与えてあげるかを考えればよいことになります。

 Springフレームワークにはjavax.ejb.SessionBeanインターフェイスを実装したアダプタークラスAbstractStatelessSessionBeanが用意されています。上記のように、getBeanFactory()メソッドを経由して、Spring管理化のビーンを取得することができますので、ここではこれを利用することにしましょう。

 セッションビーンの実装にSpringフレームワークのクラスへの依存間関係が生じますが、Java EE 5になったら、POJOのサービス・オブジェクトの実装だけが必要になり、このEJBの実装は捨ててしまえばいいわけですから、ここではSpringのAbstractStatelessSessionBeanへの依存性に神経質になる必要はないでしょう。

 ここまでで来てしまえば、EJB実装に必要なリモート/ローカルインターフェイスやホームインターフェイス、デプロイメント記述子(ejb-jar.xml)は、XDocletによって自動生成させてあげることができます。以下は、antコマンドからXDocletを呼び出す実行例です。

$ ant ejb.xdoclet
Buildfile: build.xml

ejb.xdoclet:
:
BUILD SUCCESSFUL
Total time: 2 seconds

$ find build/ -type f
build/ejb/ejb-jar.xml
build/ejb/sun-ejb-jar.xml
build/generated/com/example/business/CustomerServiceLocal.java
build/generated/com/example/business/CustomerServiceLocalHome.java
build/generated/com/example/business/CustomerServiceRemote.java
build/generated/com/example/business/CustomerServiceRemoteHome.java
build/generated/com/example/business/CustomerServiceSessionBean.java

 実際には、上記の各種ファイルを自動生成するためには、リスト3のCustomerServiceSessionBeanに対してXDocletが認識できるメタデータ(@で始まるjavadoc形式のタグ)をいくつか記述する必要があります。

 XDocletに必要なantビルドファイルの記述方法については、サンプルコードのアーカイブファイルに含まれるbuild.xmlファイルを参照してください。

   カスタムDocletでEJBを自動生成する

 現在のところXDocletはまだJDK 1.5のアノテーションには対応していません。そのため、リスト3のEJB本体のCustomerServiceSessionBeanだけは手作業で作成する必要があります。このクラスは比較的簡単に実装できますが、アプリケーションの規模が大きくなってくるとそれなりの工数が発生します。また、サービス・オブジェクトのインターフェイスが変更になれば、それに同期してEJBのインターフェイスも修正しなければなりません。

 そこで、上記のEJB自動生成の仕組みを一歩進めて、リスト3のEJB本体も自動生成することを考えます。そうすればJava EE 5と同様、EJBを全く意識せずに、サービス・オブジェクトをEJBコンテナで動作させることができるのです。

 セッション・ビーンの自動生成の入力データとしては、リスト1のサービス・インターフェイスにXDocletのトランザクション属性やアクセス制御属性を含んだものを利用します。

リスト4 CustomerServiceインターフェイス
public interface CustomerService {

    /**
     * @ejb.transaction type="Required"
     */
    public List findCustomer(CustomerCriteria criteria);
    
    /**
     * @ejb.transaction type="Required"
     * @ejb.permission role-name="manager"
     */
    public List deleteCustomer(int id, CustomerCriteria criteria);
}          

 Javaのソースコードを読み込んで別のソースファイルを生成するためには、javadocコマンドを用いた、カスタムDocletを作成するのが最も簡単です。javadocコマンドはantコマンドからも実行できますので、以下のような使い方を想定するものとします。

リスト5 EJB本体を生成するantターゲットの定義
  <target name="ejb.create" depends="compile"
          description="EJB本体を生成します">
    <!-- Docletにより、EJB本体を生成            -->                
    <!-- (ex. Foo.java -> FooSessionBean.java) -->                
    <javadoc 
      failonerror="true" 
      encoding="${source.encoding}"
      verbose="true">                                         
      <doclet name="com.example.tool.CreateSessionBeanDoclet"
              pathref="class.path">                                      
        <param name="-dir" value="${gen.src.dir}"/>
        <param name="-context" value="application-context.xml"/>
      </doclet>                                                          
      <!-- Session Beanを生成する対象サービスのインタフェース -->        
      <fileset dir="${src.dir}" defaultexcludes="yes">
        <include name="**/*Service.java"/>
      </fileset>
      <classpath refid="class.path"/>
    </javadoc>
  </target>

 カスタムDoclet CreateSessionBeanDocletの-dirオプションには生成したセッション・ビーンのソースを保存するディレクトリを指定し、-contextオプションにはEJBコンテナで使用するSpringの設定ファイル名を指定するものとします。

 カスタムDocletの作成はそれほど難しいものではありません。Docletにstart(RootDoc)メソッドを定義すると、Docletに読み込まれたソースコードをJavaからナビゲートしやすい形式にしたメタオブジェクトとしてアクセスできるようになります。メタオブジェクトは、RootDocを頂点として、ClassDoc(クラスのメタオブジェクト)の配列が取得でき、ClassDocからMethodDoc(メソッドのメタオブジェクト)の配列が取得できます。従って、ClassDoc、MethodDocのメタ情報を基にセッション・ビーンのソースの断片を生成するソースジェネレータクラスとして、それぞれClassGeneratorクラス、MethodGeneratorクラスを用意すればよいことになります。

図1 EJB自動生成のためのカスタムDocletの構成

 ソースコードの自動生成といっても、実際にはあらかじめ用意しておいたテンプレートファイルの変数に対して、得られたメタオブジェクトの情報を埋め込んでいくだけです。例えば、セッション・ビーンのメソッド部分については、以下のようなテンプレートを用意します。

リスト6 MethodGeneratorクラス用テンプレート
/**
     * %METHOD_COMMENT%
     *
     * %TAGS%
     * @ejb.interface-method
     */
    public %RETURN_TYPE% %METHOD_NAME%(%PARAM_SPEC%) %THROW_SPEC% {
        ContextUtil.setEJBContext(getSessionContext());

        %RETURN_STAT%service.%METHOD_NAME%(%PARAM_CALL%);
    }    

 MethodGeneratorクラスのgenerate()メソッド内では、リスト6のテンプレートの%変数名%の部分をメタオブジェクトの情報で置き換えていきます。

リスト7 MethodGeneratorのgenerate()メソッド
class MethodGenerator {
    /** MethodDoc:メソッドのメタオブジェクト */
    private MethodDoc methoddoc;
    /** メソッド・テンプレート */
    private static String template;

    public String generate() {
        // MethodDocからテンプレート変数用の情報を取得する
        String methodComment = methoddoc.commentText();
        String tags = generateTags();
        String returnType = methoddoc.returnType().toString();
        String methodName = methoddoc.name();
        String paramSpec = generateParamSpec();
        String throwSpec = generateThrowSpec();
        String returnStat = returnType.equals("void") ? "" : "return ";
        String paramCall = generateParamCall();

        // テンプレートの変数に値を埋める
        String gensrc = template;
        gensrc = gensrc.replaceAll("%METHOD_COMMENT%",  methodComment);
        gensrc = gensrc.replaceAll("%TAGS%",  tags);
        gensrc = gensrc.replaceAll("%RETURN_TYPE%", returnType);
        gensrc = gensrc.replaceAll("%METHOD_NAME%", methodName);
        gensrc = gensrc.replaceAll("%PARAM_SPEC%", paramSpec);
        gensrc = gensrc.replaceAll("%THROW_SPEC%", throwSpec);
        gensrc = gensrc.replaceAll("%RETURN_STAT%", returnStat);
        gensrc = gensrc.replaceAll("%PARAM_CALL%", paramCall);
        return gensrc;
    }
        :
}    

 ここのサンプルコードのアーカイブファイルにはCreateSessionBeanDocletの完全な実装が含まれています。同梱されているREADME.txtファイルの内容に従って、必要なライブラリを配備した後、ターゲットejb.createを指定してantコマンドを実行してみて下さい。リスト3のEJB本体のソースが生成されることを確認できると思います。

$ ant ejb.create
Buildfile: build.xml
       :
ejb.create:
  [javadoc] Generating Javadoc
  [javadoc] Javadoc execution
       :
BUILD SUCCESSFUL
Total time: 4 seconds

$ find build/generated/ -type f 
build/generated/com/example/business/CustomerServiceSessionBean.java

  1/2

 INDEX

第4回 疑似EJB 3.0環境をSpringとXDocletで準備する
Page1
サービス・オブジェクトをPOJOでデザインする
POJOのサービスオブジェクトをEJBでラップする
カスタムDocletでEJBを自動生成する
  Page2
Springプロキシー機能を利用してEJBサービスオブジェクトを取得する
EJB 3.0に移行可能なインターセプターを実装する
Tomcatでもトランザクションやアクセス制御の動作確認をするためには


Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間