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

  Page.1 Page.2

   Springプロキシ機能を利用して
 EJBサービス・オブジェクトを取得する

 Springフレームワークを利用すると、たとえサービスオブジェクトがEJBとして実装されたものであっても、それがあたかもPOJOであるかのようにクライアント側に見せることができます。しかも、そのためのコーディング作業は一切必要なく、Springフレームワークが提供するLocalStatelessSessionProxyFactoryBeanクラスを利用して、以下のような定義ファイルを用意するだけです。

リスト8 セッション・ビーン取得用Spring定義
<bean name="CustomerService"
        class=
"org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName">
<value>ejb/CustomerService
</value>
</property>
    <property name="resourceRef"><value>true</value></property>
    <property name="businessInterface">
      <value>com.example.business.CustomerService</value>
    </property>
  </bean> 
   

 この定義のビーンが取得された場合、LocalStatelessSessionProxyFactoryBeanは以下の手続きを実行します。

  • JNDI名“java:comp/env/ejb/CustomerService”を用いて、JNDIルックアップ処理を実行し、EJBホームインターフェイスCustomerServiceLocalHomeを取得する(ルックアップ・サービス)
  • EJBホームインターフェイスCustomerServiceLocalHomeのcreate()メソッドを呼出し、EJBローカルインターフェイスCustomerServiceLocalを取得する
  • EJBローカルインターフェイスCustomerServiceLocalをラップし、指定されたCustomerServiceインターフェイスを実装したプロキシーをランタイムに生成し、そのプロキシーを返す(ビジネス・デレゲート)

 これにより、クライアントからは、サービスオブジェクトが単なるPOJOなのか、EJBでラップされたものなのかを意識する必要がないわけです。リッチクライアントのようにEJBをリモートアクセスしなければならない場合は、LocalStatelessSessionProxyFactoryBeanの代わりにSimpleRemoteStatelessSessionProxyFactoryBeanを用いてSpring設定ファイルを定義すれば、リモート・クライアントにおいてもEJBはPOJOとしてアクセスすることができます。

 クライアントからEJBを経由して、POJOなサービスオブジェクトにアクセスしたときのランタイム構成は図2のようになります。

図2 Springを利用したEJB 3.0疑似環境の構成 (クリックすると拡大します)

 POJOのサービスオブジェクトにたどり着くまで、非常に多くのクラスが関連していますが、実際にアプリケーションとして開発しなければならないのは、サービス・インターフェイスCustomerServiceと、それを実装したPOJOなサービス・オブジェクトCustomerServiceImplだけであることに注意して下さい。そして、Java EE 5環境では、このPOJOのサービス・オブジェクト部分だけが必要で、それらがそのままJava EE 5環境でも再利用できるのです。

   EJB 3.0に移行可能なインターセプターを実装する

 次はインターセプターを実装して、サービスオブジェクトに特定の処理を織り込むことを考えます。インターセプターを任意のオブジェクトに織り込む仕組みは、Java EE 5を待たなくてもSpringフレームワークを利用することでその恩恵を得ることができます。しかし、残念なことにSpringとEJB 3.0のインターセプターは、そのAPIが異なっています。

 Springフレームワークにおいてインターセプターを実装する場合は、AOP Allianceに基づいたMethodInterceptorインターフェイスを実装し、invoke()メソッドを定義する必要があります。

リスト9 Springにおけるインターセプターの実装例
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation inv) throws Throwable {
        // 前処理(before advice)
             :
        Object result = inv.proceed(); // ターゲット・メソッドを実行

        // 後処理(after advice)
             :
        return result;
    }
   

 一方、EJB 3.0におけるインターセプターは、POJOとして実装でき、前後処理のロジックを実装するメソッド名も自由な名前が付けられることになっています。

リスト10 EJB 3.0におけるインターセプターの実装例
public class MyInterceptor {
    @AroundInvoke // メソッド名は任意
    public Object adviseSomething(InvocationContext inv) throws Exception {
        // 前処理(before advice)
             :
        Object result = inv.proceed(); // ターゲット・メソッドを実行

        // 後処理(after advice)
             :
        return result;
    }
   

 どちらの場合も前処理と後処理の間にターゲットのメソッドを実行するためにproceed()メソッドを呼び出すところは非常によく似ていますが、proceed()メソッドが定義されたコンテキスト・オブジェクトのインターフェイスも異なります。できれば、インターセプターの実装もJava EE 5以降の環境で再利用できるようにしたいものです。そこで、図3に示すようなSpringとEJB 3.0の両方で利用可能なハイブリッド形式でインターセプターを設計するようにします。

図3 Spring、EJB 3.0共用インターセプターの構成 (クリックすると拡大します)

 例えば、ビジネス・メソッドの呼出し引数と戻り値をログ出力するTracerクラスを実現する場合、実際のログ出力のコードはEJB 3.0でも再利用できるTracerクラスに実装し、Spring仕様のTracerInterceptorクラスは、MethodInvocationオブジェクトをInvocationContextオブジェクトに変換し、Tracerをデレゲート呼出しするように実装します。

リスト11 TracerInterceptorの実装例
public class TracerInterceptor implements MethodInterceptor {
    private Tracer tracer = new Tracer();

    public Object invoke(MethodInvocation inv) throws Throwable {
        // コンテキスト変換:Spring用→EJB 3.0用
        InvocationContext invocationContext = new InvocationContextImpl(inv);
        return tracer.trace(invocationContext);
    }
   

 インターセプターをサービス・オブジェクトのビジネス・メソッドに織り込むには、以下のようなSpring用のビーン定義を追加します。

リスト12 インターセプターのSpring定義
<!-- トレース出力インターセプターのポイントカット定義 -->
  <bean id="tracerAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
      <bean class="com.example.interceptor.trace.TracerInterceptor"/>
    </property>
    <property name="pattern">
      <value>.+Service\..+</value>
    </property>
  </bean>

  <!-- 自動プロキシーの利用 -->
  <bean id="autoProxyCreator"
        class="org.springframework.aop.framework.autoproxy.
DefaultAdvisorAutoProxyCreator"/> 
  

 目的のインターセプターをどのクラスのどのメソッドに織り込むかの定義には、ReqexpMethodPointcutAdvisorを利用して、ポイントカット定義を行います。patternプロパティには、<クラス名>.<メソッド名>の形式で評価する正規表現を記述します。このpatternにマッチしたメソッドにのみ対象のインターセプターが織り込まれます。上記の例では、クラス名が“Service”で終るクラスのすべてのメソッドがポイントカットの対象になることを宣言しています。また、PointcutAdvisorを定義する場合には、Spring管理化のビーン取得ときにポイントカットの評価が行われるようにするため、DefaultAdvisorAutoProxyCreatorも宣言しておきます。

 上記の定義により、CustomerServiceのメソッドが呼ばれるたびに、ログに以下のようなトレース・ログが現れるようになります。

リスト13 Tracerインターセプターによるログ出力の例
[#|2005-08-06T15:03:47.856+0900|INFO|sun-appserver-pe8.1_02|com.example.interceptor.trace.Tracer|_ThreadID=13;|ユーザ[user1]によりメソッド[findCustomer]を実行します:SERVICE=com.example.business.CustomerServiceImpl, PARAMETERS=[{name=, company=}]|#]

[#|2005-08-06T15:03:47.856+0900|INFO|sun-appserver-pe8.1_02|com.example.interceptor.trace.Tracer|_ThreadID=13;|ユーザ[user1]によるメソッド[findCustomer]が完了しました:SERVICE=com.example.business.CustomerServiceImpl, RESULT=[[{id=1, name={last=小田, first=拓郎}, ...}]]|#] 
   


   Tomcatでもトランザクションや
 アクセス制御の動作確認をするためには

 これまで述べてきた方法により、サービス・オブジェクトは、EJBを含めたEARアーカイブを作成すれば、フルスペックのアプリケーション・サーバで動作させることができ、またPOJOのままのサービス・オブジェクトを含むWARアーカイブを作成すれば、TomcatなどのWebコンテナ上でも動作せることができます。この特徴を生かすと、開発サイクルを効率的に進めるうえで非常に役に立ちます。例えば、非常に短い周期でアプリケーションを修正しながら動作確認を繰り返す場合には、デプロイのオーバーヘッドが小さいWAR形式を使用し、ある程度実装したユースケースがまとまった段階で、EJBを含めたEAR形式を使用するといった構成の切替が簡単にできるからです。

 しかしながら、先の例では、トランザクション管理やアクセス制御の機能にEJBコンテナの機能を前提としていました。

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

 上記のXDoclet用のメタタグはサービスオブジェクトをEJB化したときにだけ機能し、Tomcat上では同一の振る舞いを再現することはできません。もし、開発環境にJDK 1.5が利用できるなら、サービス・インターフェイスには、Springのインターセプターが認識できるアノテーション形式のメタデータを使用し、サービス・インターフェイスからEJBを生成する際に、アノテーションからXDocletのタグに変換できるように、カスタムDocletを拡張することも考えられます。Springはバージョン1.2から、アノテーション@Transactionalを用いてUserTransactionを制御できるようになっています。一方、アクセス制御の仕組みは現在のところSpringには含まれておらず、SpringのサブプロジェクトAcegiを別途入手する必要があります。実際には、Acegiを設定するのは非常に面倒なので、アクセス制御に関してはEJB 3.0のアノテーション@RolesAllowedを認識するSecurityInterceptorを自作してしまった方が早いでしょう。JDK 1.5のアノテーションを前提とした場合のトランザクションおよびアクセス制御のメタデータ表現は例えば以下のようになります。

リスト14 JDK 1.5のアノテーションを利用する場合
    @Transactional(propagation=Propagation.REQUIRED)
    @RolesAllowed("manager")
    public List deleteCustomer(int id, CustomerCriteria criteria);

 もし、JDK 1.4環境しか利用できない場合でも、JakartaプロジェクトのCommons Attributesを利用することで、JDK 1.5のアノテーションと同等のメタデータをメソッドやクラスに宣言することができます。Commons Attributesを利用した場合のトランザクションおよびアクセス制御定義は以下のようになります。

リスト15 JDK 1.4でCommons Attributesを利用する場合
    /**
     * @@DefaultTransactionAttribute
     (propagationBehaviorName="PROPAGATION_REQUIRED)
     * @@RolesAllowed("manager")
     */
    public List deleteCustomer(int id, CustomerCriteria criteria);

 ここでは、前者のアノテーションを利用する方針を採用し、EJBとPOJOの両方でトランザクションとアクセス制御の振舞に互換性を持たせることにします。

 まず、EJB側の対応として、先ほどのCreateSessionBeanDocletを改良し、サービス・インターフェイスに宣言されたアノテーションを読み込んで、XDocletのタグに変換する機能を加えます。この目的のためにAnnotationUtilクラスを作成し、MethodGeneratorクラスから利用するようにします。

リスト16 アノテーション→XDocletタグ変換の例
public class AnnotationUtil {
    private static final String TRANSACTIONAL =
            "org.springframework.transaction.annotation.Transactional";
    private static final String ROLES_ALLOWED =
            "com.example.interceptor.security.RolesAllowed";

    public static String convertToTags(MethodDoc methoddoc) {
        StringBuffer buf = new StringBuffer();
        // メソッドのアノテーションを取得する
        AnnotationDesc annotations[] = methoddoc.annotations();
        for (int i = 0; i < annotations.length; i++) {
            String type = annotations[i].annotationType().qualifiedName();
            if (type.equals(TRANSACTIONAL)) {
                // @Transactionalをタグ@ejb.transactionに変換する
                buf.append(convertToTransactionTag(annotations[i]));
            }
            else if (type.equals(ROLES_ALLOWED)) {
                // @RolesAllowedをタグ@ejb.permissionに変換する
                buf.append(convertToPermissionTag(annotations[i]));
            }
        }
        return buf.toString();
    }

 上記のように、JDK 1.5からMethodDocやClassDocからAnnotationDescというアノテーションのメタオブジェクトが取得できるようになっているため、これを利用して、XDocletタグへの変換をすればよいことが分かります。

 一方、Tomcat上でトランザクションを扱う場合は、Springフレームワークが提供するTransactionInterceptorがサービス・オブジェクトに織り込まれるように、Spring定義ファイルを用意します。

リスト17 Tomcat用Spring TransactionInterceptorの定義例
<!-- JTAトランザクション・マネージャ -->
  <bean id="jtaTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransactionName">
      <value>java:comp/env/UserTransaction</value>
    </property>
  </bean>

  <!-- トランザクション管理のインターセプター -->
  <bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
    <property name="transactionManager">
      <ref bean="jtaTransactionManager"/>
    </property>
    <property name="transactionAttributeSource">
      <bean class="org.springframework.transaction.
annotation.AnnotationTransactionAttributeSource"/>
    </property>
  </bean>

  <!-- トランザクション管理のポイントカット定義 -->
  <bean id="transactionAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
      <ref bean="transactionInterceptor"/>
    </property>
    <property name="pattern">
      <value>.+Service\..+</value>
    </property>
  </bean>

 また、先ほど述べたように、Springにはアクセス制御用のインターセプターがありませので、Tomcat上でアクセス制御が行えるように、新たにSecurityInterceptorクラスを作成します。SecurityInterceptorクラスの実装は、@RolesAllowedに宣言されたロール名について、isUserInRole()メソッドをチェックし、結果がtrueならターゲットのメソッドを実行し、falseならSecurityExceptionをスローするという単純なものです。また、このインターセプターは、Java EE 5環境で再利用する必要はないため、先ほどのようなハイブリッド形式にする必要はありません。

リスト18 Tomcat用SecurityInterceptorの実装例
public class SecurityInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation inv) throws Throwable {
        // 対象Methodからアノテーション@RolesAllowedを取得する
        RolesAllowed annotation =
                inv.getMethod().getAnnotation(RolesAllowed.class);
        if (annotation != null) {
            // 宣言されたロール名を取得する
            String roles[] = annotation.value();
            boolean allowed = false;
            for (int i = 0; i < roles.length; i++) {
                // 許可されたユーザかチェック
                if (ContextUtil.isUserInRole(roles[i])) {
                    allowed = true;
                    break;
                }
            }
            if (!allowed) {
                throw new SecurityException(
                        "Client not authorized for this invocation.");
            }
        }
        return inv.proceed(); // ターゲットメソッドを実行する
    }
}

 TransactionInterceptorの場合と同様に、Springの設定ファイルを定義してすべてのビジネス・メソッドにSecurityInterceptorが織り込まれるように設定します。

リスト19 Tomcat用SecurityInterceptorの定義例
<!-- アクセス制御のインターセプター -->
  <bean id="securityInterceptor"
        class="com.example.interceptor.security.SecurityInterceptor"/>

  <!-- アクセス制御のポイントカット定義 -->
  <bean id="securityAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
      <ref bean="securityInterceptor"/>
    </property>
    <property name="pattern">
      <value>.+Service\..+</value>
    </property>
  </bean>

 以上で、EJBを使う場合と使わない場合それぞれについてランタイム環境の準備が整いましたので、この2つの環境を切替える方法を考えます。EJBを含むEARファイルとPOJOのままのWARファイルを切替えるためには、それぞれで必要なSpring定義ファイルを分割し、ファイル単位で使用と未使用を切替えるとよいでしょう。

 リスト17リスト19のインターセプターの定義はTomcatの場合にしか使いませんので、web-only-context.xmlという名前のファイルでまとめておきます。一方、リスト8のEJB取得用の定義はEJBを使用する場合にしか使いませんので、ejb-local-context.xmlという名前のファイルにまとめておきます。Springは分割された複数の設定ファイルを読み込んでマージしてくれますので、antコマンドでWARファイルを作成する場合に、それぞれに必要な定義ファイルを取捨選択すればよいことになります。

リスト20 AntビルドファイルにおけるWARファイル作成の定義例
<target name="war" depends="compile" description="WARファイルを生成します">
    <war destfile="${build.dir}/${war}"
         webxml="${war.dir}/WEB-INF/web.xml">
      <webinf dir="${war.dir}/WEB-INF" defaultexcludes="true">
        <include name="classes/*"/>
        <include name="managed-beans.xml"/>
        <include name="navigation.xml"/>
        <!-- EJBの場合:-->
        <include name="ejb-local-context.xml"/>
        <!-- -->
        <!-- Tomcatの場合:
        <include name="web-only-context.xml"/>
        <include name="application-context.xml"/>
        -->
      </webinf>
         :
    </war>
  </target>

 以上、Springフレームワークを利用して、EJB 3.0と同じようにビジネス・ロジックをPOJOで実現する手法について説明しました。POJOで実現されたコンポーネントは長期的にはJava EE 5への移行を容易にするだけでなく、TomcatなどのWebコンテナを用いて簡単に動作確認することもできます。実際の開発プロジェクトでは、ここで紹介した一部の手法だけを採用すれば十分かもしれません。あるいは、もっと開発効率を追求できる可能性があれば、この手法をさらに拡張してみることも検討してみてください。

 次回は、本連載の最終回となるHibernateを利用したインテグレーション層の開発手法についての解説です。

2/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 記事ランキング

本日 月間