japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年10月15日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2006年5月2日 10:00

アノテーションとアスペクトによる宣言型プログラミングの試み

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!

はじめに

 業界全体がより生産性の高いソフトウェア開発手法を追求する中で、Javaコミュニティは、エンタープライズ開発の技術的に難しい問題(分散トランザクション管理、並列性、オブジェクト分散など)に対するソリューションとして、J2EEに期待を寄せてきました。J2EEの背後にある考え方、つまり「複雑なエンタープライズサービスの実装はアプリケーションサーバーのベンダが行い、それをビジネスアプリケーションの開発者が利用する」という考え方は、非常に合理的です。J2EE(特にEJB)は、エンタープライズJavaアプリケーション構築のための優れたプラットフォームを提供してきました。

 この成功の一部を支えているのは、宣言型プログラミングが可能になったことです。宣言型プログラミングとは、インフラストラクチャサービスの明示的なコーディングの中にビジネスロジックを組み込むのではなく、インフラストラクチャサービスを宣言することによってプログラムを開発するスタイルです。この開発スタイルの真価はEJBによって証明されました。EJBにより、トランザクションやセキュリティといったエンタープライズ機能を配備記述子の中で宣言し、コンテナから制御できるようになったのです。

 しかし、ここ数年、EJBに由来する一連の問題がチームの生産性に影を投げかけていると考える開発者が増えつつあります。EJBはそれぞれが複数のインターフェイスを伴い、それを配備記述子の中で宣言し、JNDIなどからアクセスする必要があります。コンテナの外でEJBの単体テストを行おうとするとさらに面倒なことが起こり、EJBは純粋な意味でのオブジェクト指向開発から逸れつつあります。EJBをめぐる論争については、関連記事を参照してください。

必要な環境

EJB 3.0について

 EJB 3.0は、次の仕組みを用いてエンタープライズ開発を支援することを目指しています。

  • エンタープライズサービスを宣言的に要求するメタデータアノテーションの導入
  • アノテーションによる依存性/リソース注入
  • EJB固有のインターフェイスからのエンタープライズBeanの分離
  • 軽量なオブジェクト関係マッピングによる持続性の簡易化

 これは、EJBの開発、テスト、メンテナンスに取り組んできたEJB開発者にとっては大きな福音です。EJB 3.0を使うと、エンタープライズBeanの作成は、従来の普通のJavaオブジェクト(Plain Old Java Object:POJO)と同じくらい単純化されます。特別なアノテーションを用いてEJBとして宣言し、エンタープライズサービスを要求するだけで済むのです。以下は、EJB 3.0 Public Draftに記述されているEJBの例です。

@Stateful
public class CartBean implements ShoppingCart
{
    private float total;
    private Vector productCodes;
    public int someShoppingMethod(){...};
    ...
}

 EJB 3.0の仕様は、基本的に、開発者が求めるのは何にでも対応可能な重量級ソリューションではなく、必要な範囲のエンタープライズサービスだけをカバーし、しかも簡単に使える軽量ソリューションである、という認識に立っています。この要件を満たすために、EJB 3.0では、エンタープライズBeanとEJB APIを分離することが特別重要な意味を持ちます。また、このソリューションによって興味深い副次効果がもたらされます。つまり、EJBをさまざまなEJBコンテナで実行できることはもちろんですが、エンタープライズサービスの宣言に使われるEJB 3.0(JSR 220)と共通アノテーション(JSR 250)を認識するフレームワークさえであれば、どんなアプリケーションフレームワークでもEJBを実行できるわけです。

 本稿では、宣言型プログラミング、EJB、アスペクト、アノテーションの詳しい説明は省きます。それよりも、これらのテクノロジの相互関係に焦点を当て、これを斬新な方法で組み合わることでアプリケーション開発がいかに簡素化されるかを示します。

 本稿では、EJB 3.0互換のBeanを作成し、このBeanを簡単なアスペクトを用いて配備し、宣言的なトランザクション管理、セキュリティ、リソース注入を実現します。この演習で次のことを学習できます。

  • アスペクトの応用例(依存性注入、セキュリティ、トランザクション)を知る。
  • EJB 3.0とその背景にある考え方に慣れる。
  • EJBを特定APIから分離することでEJB 3.0対応サービスの実装が軽量化され、EJB以外のフレームワークにも応用可能になることを理解する。

サンプルアプリケーション:航空券予約

 以下、本稿では航空券予約システムを例にとり、アスペクトとアノテーションを用いて依存性注入、セキュリティ、トランザクション管理を実装することを考えます。このアプリケーションには2つの機能しかありません。つまり、ユーザーが便名を検索する機能(図1)と、予約を入れる機能(図2)です。どちらの操作も、登録済みユーザーだけが実行できるよう保護されます。また、この「旅券予約」操作では2つの便(往路と復路)の予約を必須とするため、トランザクション的な処理が必要になります。つまり、2つの予約をひとまとまりと見なし、両方が成功するか失敗するようにしなければなりません。

図1 便名検索:最初に、ユーザーは自分の要件に合った便を検索する
図1 便名検索:最初に、ユーザーは自分の要件に合った便を検索する
図2 旅券予約:次に、ユーザーは往路便と復路便の両方を予約する。両方の予約は共に成功するか失敗するかのどちらかでなければならない
図2 旅券予約:次に、ユーザーは往路便と復路便の両方を予約する。両方の予約は共に成功するか失敗するかのどちらかでなければならない

 この簡単なWebアプリケーションは、2つのサーブレット、1つのサービスファサード、そして1つのDAOレイヤから構成されます(図3を参照)。

図3 航空券予約システムのアーキテクチャ:3つのコンポーネントが連携してユーザーの予約を処理する
図3 航空券予約システムのアーキテクチャ:3つのコンポーネントが連携してユーザーの予約を処理する

 リソース設定、セキュリティ、トランザクション管理という分野横断的な関心事は、AspectJ 1.5 M3で実装されているアスペクトによって配備されます(このアスペクトにより、Java 5アノテーションの中で宣言されたビヘイビアが注入されます)。本稿の冒頭で紹介しているリンクからサンプルコードをダウンロードし、Maven 2.0でビルドしてください。

リソース注入

 現在のEJB 3.0ドラフト仕様では、リソースを@Resourceアノテーションで宣言し、そのリソースをコンテナからEJBに注入することができます(@Resourceアノテーションは共通アノテーション仕様の中で規定されています)。依存性注入とは、オブジェクトの依存性をオブジェクト自身が明示的に作成する代わりに、オブジェクト外部のエンティティから提供(注入)する手法です。これを茶化して「ハリウッド方式」と呼ぶこともあります。本人からのアクセスは認めず、「用があるときはこちらからお電話します」というわけです。

 サンプルのTravelAgencyServiceImplクラスを見てみましょう。このクラスでは、データの持続性を実現するために、IFlightDAOインターフェイスの実装を見つける必要があります。これは伝統的に、Factory、Singleton、Service Locatorなどのパターンを用いて実現されます。ソリューションの一例は次のような形になります。

public class TravelAgencyServiceImpl implements ITravelAgencyService
{
    public IFlightDAO flightDAO;

    public TravelAgencyServiceImpl()
    {
        flightDAO = FlightDAOFactory.getInstance().getFlightDAO();
    }

    public void bookTrip(long outboundFlightID,
                         long returnFlightID, int seats)
            throws InsufficientSeatsException
    {
        reserveSeats(outboundFlightID, seats);

        reserveSeats(returnFlightID, seats);
    }
}

 この実装では、特別なファクトリクラスが作成されます。容易に想像されるように、このクラスは、どこかに保存されている設定情報を読み取り、IFlightDAOの実装をどんな形で生成すればよいかを知ることになります。サービスがその依存性を明示的に作成する代わりにコンテナから依存性を注入する場合は、設定を含むオブジェクト生成の詳細がコンテナに委譲されます。これにより、アプリケーションに必要なコンポーネントを設定に応じて簡単に「配線」し、決まり切ったシングルトンやファクトリコードの多くを排除することが可能となります。

 このクラスが、JSR 250リソースアノテーションで宣言されたIFlightDAOの実装に対して依存性を持つ場合、クラスの実装は次のようになります。

public class TravelAgencyServiceImpl implements ITravelAgencyService
{
    @Resource(name = "flightDAO")
    public IFlightDAO flightDAO;

    public void bookTrip(long outboundFlightID,
                         long returnFlightID, int seats)
            throws InsufficientSeatsException
    {
        reserveSeats(outboundFlightID, seats);

        reserveSeats(returnFlightID, seats);
    }
}

 この例では、「flightDAO」というリソースの適切な実装が、コンテナによってサービスクラスに提供されます。ところで、EJB 3.0がまだリリースされていない現在、リソース注入を利用するにはどうすればよいのでしょう。SpringやPico Containerといった軽量コンテナを使えば依存性注入を実現できますが、筆者が知る限り、JSR 250リソースアノテーションを用いて注入条件を指定する軽量コンテナはまだ存在しません(いずれ、この場で紹介されるとは思いますが)。

 1つの方法として、アスペクトを用いて依存性注入を実装することが考えられます。@Resourceアノテーションをこの目的で使えば、その実装はEJB 3.0による方法と一貫性を持ち、EJB 3.0の実装と前方互換となります。実際、これはそれほど難しくありません。次のコードはAspectJで作成したアスペクトです。これは、@Resourceアノテーションで指定されたフィールドを注入します。

@Aspect
public class InjectionAspect
{
    private DependencyManager manager = new DependencyManager();

    @Before("get(@Resource * *.*)")
    public void beforeFieldAccesses(JoinPoint thisJoinPoint)
        throws IllegalArgumentException, IllegalAccessException
    {
        FieldSignature signature =
            (FieldSignature) thisJoinPoint.getSignature();

        Resource injectAnnotation =
            signature.getField().getAnnotation(Resource.class);

        Object dependency = manager.resolveDependency(
            signature.getFieldType(), injectAnnotation.name());

        signature.getField().set(thisJoinPoint.getThis(), dependency);
    }
}

 このアスペクトは、プロパティファイル内から実装クラスを探し(このロジックはDependencyManagerオブジェクトにカプセル化されています)、見つけた実装クラスを@Resourceアノテーションで指定されたフィールドに注入したのちに、フィールドアクセスを行います。なお、この依存性注入はJSR 250とEJB 3.0のやり方をまねています。言うまでもなく、この実装は完全なものでありませんが、JSR 250互換の方法でリソース注入を実現するとき、必ずしもEJBを使わなくてよいことが分かります。

セキュリティ

 JSR 250とEJB 3.0は、リソース注入に加え、アノテーションを用いてセキュリティメタデータを表現することも実現しています。javax.annotation.securityパッケージの中で規定されている5つのアノテーション(RunAsRolesAllowedPermitAllDenyAllRolesReferenced)をメソッドに適用することで、セキュリティ要件を定義できます。例えば、前掲のbookTripメソッドについて、呼び出し元のロールが「user」である場合のみ実行を許可すると宣言するには、次のようなアノテーションを記述してセキュリティ制限を課します。

public class TravelAgencyServiceImpl implements ITravelAgencyService
{
    @Resource(name = "flightDAO")
    public IFlightDAO flightDAO;

    @RolesAllowed("user")
    public void bookTrip(long outboundFlightID, long returnFlightID,
        int seats) throws InsufficientSeatsException
    {
        reserveSeats(outboundFlightID, seats);

        reserveSeats(returnFlightID, seats);
    }
}

 このアノテーションは、所定のロールを持つ呼び出し元にだけメソッドの実行を許可するようコンテナに指示します。次に、このセキュリティ制約をアプリケーションに課す簡単なアスペクトの例を紹介しましょう。

@Aspect
public class SecurityAspect
{
    @Around("execution(@javax.annotation.security.RolesAllowed 
* *.*(..))")
    public Object aroundSecuredMethods(
        ProceedingJoinPoint thisJoinPoint) throws Throwable
    {
        boolean callerAuthorized = false;

        RolesAllowed rolesAllowed =
            rolesAllowedForJoinPoint(thisJoinPoint);
        for (String role : rolesAllowed.value())
        {
            if (callerInRole(role))
            {
                callerAuthorized = true;
            }
        }

        if (callerAuthorized)
        {
            return thisJoinPoint.proceed();
        }
        else
        {
            throw new RuntimeException(
               "Caller not authorized to perform specified function");
        }
    }

    private RolesAllowed rolesAllowedForJoinPoint(
        ProceedingJoinPoint thisJoinPoint)
    {
        MethodSignature methodSignature =
            (MethodSignature) thisJoinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();

        return targetMethod.getAnnotation(RolesAllowed.class);
    }

    private boolean callerInRole(String role)
    {
        ...
    }
}

 このアスペクトは、@RolesAllowedアノテーションで指定されたメソッドの実行要求に対して必ず適用され、呼び出し元のロールがアノテーションで指定されたロールのいずれかに一致するかどうかによって、呼び出し元にメソッドの実行許可を与えるかどうかを判断します。もちろん、ユーザーのロールを調べて実行を許可する部分については、JAASやカスタムソリューションなど、任意のアルゴリズムを使うことができます。本稿のサンプルコードでは、簡略化のためにサーブレットコンテナに委譲しました。

トランザクション

 トランザクションは、並列環境におけるデータの完全性を保証する役割を担うという意味で、エンタープライズ開発の重要な要素と位置づけられます。おおざっぱに言えば、トランザクションとは、複数の操作がすべて完了するか、それとも一切完了していないか、そのどちらかであることを保証するものです。トランザクションの詳細については、筆者の以前の記事「Using Annotations with Aspects in Pre-Java 5 Versions」または関連記事を参照してください。

 リソース注入やセキュリティのアノテーションと異なり、トランザクションのアノテーションはEJB 3.0固有のもので、JSR 250の共通アノテーションでは規定されていません。EJB 3.0では、トランザクションに関連する2つのアノテーションを規定しています。1つはTransactionManagementで、もう1つはTransactionAttributeです。TransactionManagerアノテーションでは、トランザクションをコンテナ管理とするかBean管理とするかを指定します。EJB 3でこのアノテーションを省略した場合は、コンテナ管理のトランザクションと仮定されます。TransactionAttributeアノテーションは、メソッドのトランザクション伝播レベルを指定します。有効な値はmandatoryrequiredrequires newsupportsnot supportedneverで、これらは現在のトランザクションが必要かどうか、新しいトランザクションを開始するかどうかなどを指定します。

 予約操作は2ステップ(往路と復路の予約)で行われるため、この操作の完全性を保証するためにトランザクションでラップします。EJB 3.0のトランザクションアノテーションを使うと、これは次のようになります。

public class TravelAgencyServiceImpl implements ITravelAgencyService
{
    @Resource(name = "flightDAO")
    public IFlightDAO flightDAO;

    @RolesAllowed("user")
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void bookTrip(long outboundFlightID,
        long returnFlightID, int seats)
        throws InsufficientSeatsException
    {
        reserveSeats(outboundFlightID, seats);

        reserveSeats(returnFlightID, seats);
    }
}

 次に簡単なアスペクトを適用します。これでトランザクションの境界が自動的に決定されます。

@Aspect
public class TransactionAspect
{
    @Pointcut("execution(@javax.ejb.TransactionAttribute * *.*(..))")
    public void transactionalMethods()
    {}

    @Before("transactionalMethods()")
    public void beforeTransactionalMethods()
    {
        HibernateUtil.beginTransaction();
    }

    @AfterReturning("transactionalMethods()")
    public void afterReturningTransactionalMethods()
    {
        HibernateUtil.commitTransaction();
    }

    @AfterThrowing("transactionalMethods()")
    public void afterThrowingTransactionalMethods()
    {
        HibernateUtil.rollbackTransaction();
    }
}

 この実装では、HibernateパターンとUbiquitous Thread Localパターンを用いてHibernate SessionオブジェクトとTransactionオブジェクトを管理するものと仮定していますが、JTAベースの実装など、任意の実装を代わりに使うこともできます。

おわりに

 本稿では、リソース管理、セキュリティ、トランザクションといった分野横断的な問題をアスペクトとして実装するためにEJB 3.0とJSR 250のアノテーションセットを使いました。ここではいくつかのことを学びました。第一に、具体的なサンプルアスペクトを通じて、AspectJの実装を用いて分野横断的な問題をモジュール化する方法を学びました。第二に、間もなく登場するEJB 3.0の新しい考え方や概念を考察しました。第三に、ビジネスオブジェクトをEJB APIから分離すると大きな自由度が得られることを学びました。例えば、TravelAgencyServiceImplをステートレスセッションBeanにするには、次のアノテーションを追加するだけで済みます。

@Stateful
public class TravelAgencyServiceImpl implements ITravelAgencyService
{
    ...
}

 エンタープライズサービスの配備に関して、この極めて自由度の高いアプローチは、フレームワーク/コンテナ業界にさらなる競争と革新をもたらすものと思われます。

著者紹介

Rod Coffin(Rod Coffin)
テキサス州ダラス在住。Valtech Technologiesの上級顧問。MicrosoftとJavaの双方のテクノロジについて幅広い経験を有す。また、開発者、企画設計者として数多くのプロジェクトに参加。現在、オクラホマ市Java User’s Groupのモデレータとして活動。メールの宛先はrod.coffin@valtech.com。
関連テーマ
最新トップニュース
データメーション
【データメーション】
次の「Beverly Hills Chihuahua」はDVD違法コピー対策犬(10月15日)
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「情報が気持ちよく伝わる世界を作る!」/アスカティースリー株式会社(10月15日)
Graphic Design Forum
【Graphic Design Forum】
あなたならどうする - 倫理にかかわる問題 (10月14日)
エンジニアの独り言
【エンジニアの独り言】
得体の知れない情報(?)との向き合い方(9月17日)
最新テクノロジーの意外な処方箋
【最新テクノロジーの意外な処方箋】
昆虫と退屈なことについて(9月16日)
「IT の耳」
「IT の耳」
【書評】『メディアの実験集「モノサシに目印」』 ――デザインを遊ぶ(10月15日)
Eメールマーケティング事情
Eメールマーケティング事情
企業メルマガ担当者(10月15日)
日本と韓国のインターネットビジネス最新動向調査
日本と韓国のインターネットビジネス最新動向調査
日本と韓国の Blog 比較2―展望(10月15日)
百式のネットビジネス研究
百式のネットビジネス研究
iPhone 用アプリを毎日作ってソースコードごと公開している「Apps Amuck」(10月15日)
SNSをビジネスに活用しよう
SNSをビジネスに活用しよう
周到に Hype(ハイプ)を迎えないと「幻滅期」は超えられない。(後篇)(10月14日)
developer.com
developer.com
デザインパターンの使い方: Builder(10月14日)
エンジニア転職ノウハウ開発室
エンジニア転職ノウハウ開発室
職種別 採用天気予報 [08年10〜12月期](10月14日)
アイレップの SEM フロンティア
アイレップの SEM フロンティア
キーワードとプレースメントの併用で Google AdWords 広告の最適化を進めよう(10月14日)
台湾企業が席巻する電子製品製造
台湾企業が席巻する電子製品製造
蔓延する市場の不透明感、不況の今だからこそ考える生産アウトソーシング(10月10日)
IT マネジメント
IT マネジメント
「後戻りできない」 Windows 7(10月10日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/