アノテーションとアスペクトによる宣言型プログラミングの試みはじめに業界全体がより生産性の高いソフトウェア開発手法を追求する中で、Javaコミュニティは、エンタープライズ開発の技術的に難しい問題(分散トランザクション管理、並列性、オブジェクト分散など)に対するソリューションとして、J2EEに期待を寄せてきました。J2EEの背後にある考え方、つまり「複雑なエンタープライズサービスの実装はアプリケーションサーバーのベンダが行い、それをビジネスアプリケーションの開発者が利用する」という考え方は、非常に合理的です。J2EE(特にEJB)は、エンタープライズJavaアプリケーション構築のための優れたプラットフォームを提供してきました。 この成功の一部を支えているのは、宣言型プログラミングが可能になったことです。宣言型プログラミングとは、インフラストラクチャサービスの明示的なコーディングの中にビジネスロジックを組み込むのではなく、インフラストラクチャサービスを宣言することによってプログラムを開発するスタイルです。この開発スタイルの真価はEJBによって証明されました。EJBにより、トランザクションやセキュリティといったエンタープライズ機能を配備記述子の中で宣言し、コンテナから制御できるようになったのです。 しかし、ここ数年、EJBに由来する一連の問題がチームの生産性に影を投げかけていると考える開発者が増えつつあります。EJBはそれぞれが複数のインターフェイスを伴い、それを配備記述子の中で宣言し、JNDIなどからアクセスする必要があります。コンテナの外でEJBの単体テストを行おうとするとさらに面倒なことが起こり、EJBは純粋な意味でのオブジェクト指向開発から逸れつつあります。EJBをめぐる論争については、関連記事を参照してください。 必要な環境EJB 3.0についてEJB 3.0は、次の仕組みを用いてエンタープライズ開発を支援することを目指しています。
これは、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を簡単なアスペクトを用いて配備し、宣言的なトランザクション管理、セキュリティ、リソース注入を実現します。この演習で次のことを学習できます。
サンプルアプリケーション:航空券予約以下、本稿では航空券予約システムを例にとり、アスペクトとアノテーションを用いて依存性注入、セキュリティ、トランザクション管理を実装することを考えます。このアプリケーションには2つの機能しかありません。つまり、ユーザーが便名を検索する機能(図1)と、予約を入れる機能(図2)です。どちらの操作も、登録済みユーザーだけが実行できるよう保護されます。また、この「旅券予約」操作では2つの便(往路と復路)の予約を必須とするため、トランザクション的な処理が必要になります。つまり、2つの予約をひとまとまりと見なし、両方が成功するか失敗するようにしなければなりません。 図1 便名検索:最初に、ユーザーは自分の要件に合った便を検索する ![]() 図2 旅券予約:次に、ユーザーは往路便と復路便の両方を予約する。両方の予約は共に成功するか失敗するかのどちらかでなければならない ![]() この簡単なWebアプリケーションは、2つのサーブレット、1つのサービスファサード、そして1つのDAOレイヤから構成されます(図3を参照)。 図3 航空券予約システムのアーキテクチャ:3つのコンポーネントが連携してユーザーの予約を処理する ![]() リソース設定、セキュリティ、トランザクション管理という分野横断的な関心事は、AspectJ 1.5 M3で実装されているアスペクトによって配備されます(このアスペクトにより、Java 5アノテーションの中で宣言されたビヘイビアが注入されます)。本稿の冒頭で紹介しているリンクからサンプルコードをダウンロードし、Maven 2.0でビルドしてください。 リソース注入 現在のEJB 3.0ドラフト仕様では、リソースを サンプルの 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); } } この実装では、特別なファクトリクラスが作成されます。容易に想像されるように、このクラスは、どこかに保存されている設定情報を読み取り、 このクラスが、JSR 250リソースアノテーションで宣言された 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つの方法として、アスペクトを用いて依存性注入を実装することが考えられます。 @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); } } このアスペクトは、プロパティファイル内から実装クラスを探し(このロジックは セキュリティ JSR 250とEJB 3.0は、リソース注入に加え、アノテーションを用いてセキュリティメタデータを表現することも実現しています。javax.annotation.securityパッケージの中で規定されている5つのアノテーション( 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 このアスペクトは、 トランザクショントランザクションは、並列環境におけるデータの完全性を保証する役割を担うという意味で、エンタープライズ開発の重要な要素と位置づけられます。おおざっぱに言えば、トランザクションとは、複数の操作がすべて完了するか、それとも一切完了していないか、そのどちらかであることを保証するものです。トランザクションの詳細については、筆者の以前の記事「Using Annotations with Aspects in Pre-Java 5 Versions」または関連記事を参照してください。 リソース注入やセキュリティのアノテーションと異なり、トランザクションのアノテーションはEJB 3.0固有のもので、JSR 250の共通アノテーションでは規定されていません。EJB 3.0では、トランザクションに関連する2つのアノテーションを規定しています。1つは 予約操作は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パターンを用いて おわりに 本稿では、リソース管理、セキュリティ、トランザクションといった分野横断的な問題をアスペクトとして実装するためにEJB 3.0とJSR 250のアノテーションセットを使いました。ここではいくつかのことを学びました。第一に、具体的なサンプルアスペクトを通じて、AspectJの実装を用いて分野横断的な問題をモジュール化する方法を学びました。第二に、間もなく登場するEJB 3.0の新しい考え方や概念を考察しました。第三に、ビジネスオブジェクトをEJB APIから分離すると大きな自由度が得られることを学びました。例えば、 @Stateful public class TravelAgencyServiceImpl implements ITravelAgencyService { ... } エンタープライズサービスの配備に関して、この極めて自由度の高いアプローチは、フレームワーク/コンテナ業界にさらなる競争と革新をもたらすものと思われます。 著者紹介Rod Coffin(Rod Coffin)
テキサス州ダラス在住。Valtech Technologiesの上級顧問。MicrosoftとJavaの双方のテクノロジについて幅広い経験を有す。また、開発者、企画設計者として数多くのプロジェクトに参加。現在、オクラホマ市Java User’s Groupのモデレータとして活動。メールの宛先はrod.coffin@valtech.com。
|