レガシーJavaアプリケーションの保守にAOPを使用する

複雑で未知なJavaコードを処理するためのテクニック

もしJavaベースのレガシーアプリケーションを引継いで保守しなければならない場合、この記事は役に立つでしょう。著者のAbhijit Belapurkarは、レガジーアプリケーションの最も不透明である内部動作への先例のないビューを獲得するためにアスペクト指向プログラミング(AOP)を使用する方法を説明します。

Abhijit Belapurkar (abhijit_belapurkar@infosys.com), Senior Technical Architect, Infosys Technologies Limited

Abhijit Belapurkarは、インドのデリーにあるインド工科大学(IIT)のコンピューター科学の技術学位の学士を持っています。彼は約10年間、分散アプリケーション用アーキテクチャーおよび情報セキュリティーの分野で働いており、5年以上の間n-tierアプリケーションを構築するためにJavaプラットフォームを使用していました。彼は、現在インドのバンガロールにあるInfosys Technologies株式会社で、J2EEスペースのシニアテクニカルアーキテクトとして働いています。



2004年 3月 09日

一般的に、ソフトウェア・システムは十分理解されている要件の有限セットで始まります。しかしながら、ほとんどの成功したシステムが発展していくにつれて、数え切れない機能的・非機能的な面を取り込み、ますます多くの要件を引き受けます。エンタープライズ環境では、この絡み合ったモジュールにサード・パーティーのライブラリーやフレームワークを追加してしまいます。それらのすべては、システムの日常稼動の表面下で相互に作用してしまいます。要するに、簡単に保守可能な要件で始まったシステムは、たった数年の間に手におえなく、愚鈍なコードの塔(タワー)という、モンスターになってしまいます。

日常のメンテナンスや新たな改良をするため、Java開発者はこの塔に踏み込まなくてならないでしょう。もし、あなたがこの開発者なら、まずはシステム構造の詳しい知識を得ることが必要です。構造を理解することは、機能向上や今後必ず生じるであろうあらゆる問題を解決する上で鍵となるでしょう。もちろん、初めてよく知らないシステムを熟視することは簡単なことではありません。場合によっては、そのシステムに携わった開発者に質問できるかもしれませんが、できない場合もあります。しかし、たとえ開発チームに接することができたとしても、場合によってはシステムが大規模すぎて機械的な手助けなしでアクセスしたり、理解するのは大変になります。

複雑なプログラム(参考文献を参照)を理解するのに、ツールの大部分は役に立ちますが、そのうちのほとんどは高価で、その上習得するのに時間がかかり、機能範囲に制限があったりします。(これですと、ツールがニーズに合わないと何も頼るものがないでしょう。)そこで、この記事ではその代わりとなる方法を提案します。アスペクト指向プログラミングは、レガシーアプリケーションの理解やメンテナンスを含むプログラミング・シナリオの広範囲に適用できる完全なプログラミング・パラダイムです。

この記事は、AspectJのAOP、特にAspectJの静的および動的なcrosscutting(横断)技術について、一般的に精通していることを前提としているので注意して下さい。次のセクションで、AOPのcrosscutting(横断)に関する簡単な概要を説明しますが、更に詳しい情報については参考文献を参照してください。

概念的な概要

JavaベースのAOPでは、表面上様々な方法で複雑なアプリケーションをスライシングしたり、ダイシングできる柔軟性を持っており、豊富な表現言語を採用しています。JavaベースのAOP構文は、Java言語に極めて似ているので、構文を理解するのはそれほど難しくありません。一度習得してしまうと、AOPは多くのアプリケーションで使用できるプログラミング技術です。レガシーシステム内部の理解に加え、AOPを使用すると、このようなシステムをリファクターしたり拡張するのに、大きな変更をしないで済みます。この記事ではAspectJを用いて説明をしますが、これから議論するほとんどの技術は、AspectWerkzやJBossAOPのような他の人気のあるJavaベースのAOP実装にも適用できます(参考文献を参照)。

crosscutting(横断)とは

どんなアプリケーションも、複数の機能的、システム的なconcernから構成されています。機能的なものはアプリケーションの日常の使用に関連していますが、システム的なものはシステムの稼動状況とメンテナンスに関連しています。例えば、銀行向けアプリケーションの機能的なconcernには口座管理や借方/貸方操作の許可があります。また、システム的なものにはセキュリティー、トランザクション、パフォーマンスや監査ログが含まれます。たとえ、最適なプログラミング方法でアプリケーションを開発したとしても、機能的なconcernとシステム的な関係concernが多数のアプリケーションモジュールをまたがってしまい、互いに絡み合ってしまうことに結局気が付くでしょう。

crosscutting(横断)は、独立したconcernがモジュール化を保ちながら、アプリケーションの至る所に異なったpoint(ポイント)で適用するのに十分な柔軟性を保証するためのAOP技術です。このcrosscutting(横断)には、静的と動的の2つの種類が存在します。dynamic crosscutting(動的な横断)は、特定のpointで、新しい振る舞いを織り込むことにより、実行時におけるオブジェクトの振る舞いの変更をもたらします。static crosscutting(静的な横断)は、追加メソッドあるいは属性、またはその両方を挿入することにより、オブジェクトの構造を変更することを可能にします。

static crosscutting(静的な横断)の構文は、dynamic crosscutting(動的な横断)とは全く異なります。次の用語は、dynamic crosscutting(動的な横断)についてです。

  • join point(ジョインポイント) はクラスにあるメソッドのようなもので、Javaプログラムの特定の実行ポイントとなります。
  • pointcut(ポイントカット)は、特定のjoin pointを示したり、取り込んだりする言語特有のコンストラクタです。
  • adviceは、特定のpointcutに到達する時に実行されるコードの一部分(一般的には、crosscutting(横断)機能)です。
  • aspectは、pointcutとadviceとそれらの間のマッピングを定義するコンストラクタです。aspectは特定の実行ポイントで既存オブジェクトに追加機能を織り込むためにAOPコンパイラーによって使用されます。

この記事で論証するコードはすべて、dynamic crosscutting(動的な横断)を利用します。更にstatic crosscutting(静的な横断)に関して学習するには参考文献を参照してください。

AspectJでのAOP

この記事の例題を実践するには、AspectJでのAOP特有の次の機能について精通している必要があります。

  • AspectJは、AspectJとJava言語ファイルをコンパイルするajcと呼ばれるコンパイラ/バイトコードweaverを提供しています。ajcはJava VM(1.1以降)に準拠したクラスファイルを生成するのに必要なaspectを一緒に織り込みます。
  • AspectJは、特定のjoin pointが到達してはならないことを指定するaspectをサポートしています。もしajcのプロセスが他の状態で終了する場合、コンパイル時の警告、あるいはエラー(aspectに依存します)をシグナル通知します。

アプリケーションとシステム分析

続くセクションでは、アプリケーションの2つの異なるのメカニズムやAOPを使用したシステム分析を学習します。まず、第1のメカニズム(静的分析と呼びますが)について、以下の手順に従って下さい。

  1. CVS(又は、あたなが使用しているバージョン管理システムのコード)からアプリケーションコードベース全体をあなたのローカル環境にチェックアウトします。
  2. AspectJ コンパイラー(ajc)を使用するためにbuildファイルを変更します。
  3. 適切な場所にaspectクラスをインクルードします。
  4. システムの全ビルドを実行します。

第2のメカニズム(動的分析と呼びますが)は、ローカル環境でシステムをビルドするだけでなく、システムの実際のユース・ケースを実行し、実行時のリフレクションからの情報を実行システムへ収集します。続くセクションでは、重要な概念を説明するのにコード例を使用して各メカニズムの詳細を見ていきます。


静的分析

以下の一般的な3つのメンテナンス・シナリオに適用させながら、レガシーアプリケーションの静的分析を実行するいくつかの技術を検証していきます。

  • インターフェース変更の影響の評価
  • デッド、あるいは到達不可能なコードの識別
  • 疎結合の開始

では、始めましょう!

インターフェース変更の影響の評価

オブジェクト指向のレガシーアプリケーションあるいはシステムは、そのクライアントへ明確に定義されたインターフェースを公開する多くのモジュールから成るべきです。それでは、既存インタフェース変更を伴う新しい要件を組み込む作業を任されたと仮定しましょう。コードに不慣れなので、最初に挑戦することは、このインターフェースの変更がシステムのクライアントに与える正確な影響を理解することです。この例での暗黙の影響は、変更されるシグニチャーごとにメソッド呼び出しを行うようにそれぞれのクライアントを変更するだけではありません。したがって、そのシステムにとって、その要件が時間と労力をかけるだけの価値があるかという検討と同様に、全てのクライアントのインターフェースを知ることは、新しい要件を実装する最良の方法を決定する上で役立つかもしれません。最初に、クライアントのインターフェースを判別するのに静的分析を使用し、その後でシステム上のインターフェース変更における影響を評価していきます。

この技術は、アスペクトのコンパイル時の警告シグナリングに基づいており、特定のジョインポイントが到達可能かどうかをシグナル通知します。このケースでは、特定のインターフェースの全メソッドへの呼び出しを取り込むためにジョインポイントを書きます。まず最初に、ローカル環境のアプリケーションコードを抽出します。そして、AspectJコンパイラーを使用し、適切な場所にaspectクラスをインクルードするためにbuildファイルを変更します。そして、システムの全ビルドを実行します。正しくジョインポイントを取り込んだ場合、コードベースのinterestのメソッドへの呼び出しをシグナル通知するコンパイル時の警告を見ることができるでしょう。

リスト1で示しているコンパイル時のaspectは、(1つ以上の)インプリメンタークラス自身を除外している間、(インプリメンターの)インターフェースのcom.infosys.setl.apps.AppA.InterfaceAを呼び出す全クラスを検出し表示します。従って、サンプルのInterfaceA、そのインプリメンタークラスのClassA、呼び出しクラスのClassBでは、aspectはClassBa.methodA()呼び出しに対してコンパイル時の警告を生成するでしょう。

リスト1: InterfaceAを使用するクラス
package com.infosys.setl.apps.AppA;
public interface InterfaceA
{
  public String methodA();
  public int methodB();
  public void methodC();
}
package com.infosys.setl.apps.AppA;
public class ClassA implements InterfaceA
{
  public String methodA()
  {
	return "Hello, World!";
  }
  public int methodB()
  {
	return 1;
  }	
  public void methodC()
  {
	System.out.println("Hello, World!");
  }
}
package com.infosys.setl.apps.AppB;
import com.infosys.setl.apps.AppA.*;
public class ClassB
{
  public static void main(String[] args)
  {
	try
	{
		InterfaceA a = 
		(InterfaceA)Class.forName("com.infosys.setl.apps.AppA.ClassA").newInstance();
		System.out.println(a.methodA());
	}
	catch (Exception e)
	{
		e.printStackTrace();
	}
  }
}
package com.infosys.setl.aspects;
public aspect InterfaceCallersAspect
{
  pointcut callerMethodA(): call (* com.infosys.setl.apps.AppA.InterfaceA.methodA(..))
   && !within(com.infosys.setl.apps.AppA.InterfaceA+);
  pointcut callerMethodB(): call (* com.infosys.setl.apps.AppA.InterfaceA.methodB(..))
   && !within(com.infosys.setl.apps.AppA.InterfaceA+);
  pointcut callerMethodC(): call (* com.infosys.setl.apps.AppA.InterfaceA.methodC(..))
   && !within(com.infosys.setl.apps.AppA.InterfaceA+);
  declare warning: callerMethodA(): "Call to InterfaceA.methodA";
  declare warning: callerMethodB(): "Call to InterfaceA.methodB";
  declare warning: callerMethodC(): "Call to InterfaceA.methodC";
}

リスト2で示すようなaspectにより、インプリメンタークラス上でのインターフェース変更の影響を測定することができます。前のリストにあったサンプルクラスについては、このaspectがClassAに対してコンパイル時の警告を生成します。

リスト2 .InterfaceAをインプリメントするクラス
package com.infosys.setl.aspects;
public aspect InterfaceImplAspect
{
  pointcut impls(): staticinitialization (com.infosys.setl.apps.AppA.InterfaceA+)
   && !(within(com.infosys.setl.apps.AppA.InterfaceA));
  declare warning: impls(): "InterfaceA Implementer";
}

デッドコードの識別

機能拡張がぽつぽつと要求されるにつれて、システムは少しずつ成長していきます。システム、あるいはアプリケーションの一部に変更をする時はいつでも、他の部分への影響を知ることが重要となります。例えば、Module A のコードをリファクタリングすると、他の場所にあるコード(クラスにあるメソッド、あるいはクラス全体のいずれか)が達成不可能(あるいはデッド)になるかもしれません。これは、ナビゲートするために制御/データフローが更新されたからです。デッドコードをそのまま放置しておくと、使わないコードでシステムが圧迫され、結局はパフォーマンス低下に結びつく可能性があります。

幸運にも、この同じテクニックは、コード強化の影響(前のセクションで示したように)がデッドまたは到達不可能なコードの識別に適用可能であるかを判別するのに使用されました。リスト1で示しているコンパル時のaspectは、呼ばれているインターフェースのメソッドをすべて発見するのに使用できるので、影響を測定することができます。コンパイル時の警告を生成しないインターフェースのどのメソッドも、デッドコードであると推測できるので、問題なく削除することができます。

疎結合の開始

ある意味では、ハードコーディングされた呼び出し、あるいは、インターフェースに対立する特定のコンポーネントへの呼び出しを持つレガシーアプリケーションを保守しなければならないかもしれません。そのようなシステムの変更(例えば、新規あるいは代わりのコンポーネントの追加など)は、かなり冗長かもしれませんし、また努力を必要とするかもしれません。幸運にも、AOPを使用して、システムから特定のコンポーネントを分離させ、汎用的なインターフェースベースのコンポーネントに置き換えることができます。これにより、実際のインプリメンターをオンザフライに接続させることができます。

リスト3に示すサンプルクラスを見て下さい。ClassAmethodAは、System.outを直接呼び出す形式でロギングをハードコーティングしています。

リスト3.ハードコーディングされたロギングの呼び出しがあるクラス
package com.infosys.setl.apps.AppA;
public class ClassA
{
  public int methodA(int x)
  {
	System.out.println("About to double!");
	x*=2;
	System.out.println("Have doubled!");
	return x;
  }
	
  public static void main(String args[])
  {
	ClassA a = new ClassA();
	int ret = a.methodA(2);
  }
}

手動で、既存のロギング呼び出しの全てを汎用的なロガー・インターフェースへの呼び出しに置き換えることは、明らかに冗長となります。よりよい対応策は、そのような呼び出し全てを検索し置き換えるために、リスト4で示すaspectを使用することでしょう。この汎用的なロガー・インターフェースはLoggerInterfaceにより提供され、ファクトリークラスのLoggerFactoryは、特定のロガー・インターフェース(リスト4のSETLogger)得るために使用されます。

リスト4.ハートコーティングされた呼び出しを置き換える汎用的なインターフェース
package com.infosys.setl.utils;
public interface LoggerInterface
{
	public void log(String logMsg, Object target);
}
package com.infosys.setl.utils;
public class LoggerFactory
{
  private static LoggerFactory _instance = null;
  private LoggerFactory(){}
  public static synchronized LoggerFactory getInstance()
  {
	if (_instance == null)
		_instance = new LoggerFactory();
	return _instance;
  }
  public LoggerInterface getLogger()
  {
	LoggerInterface l = null;
	try
	{
	    l = (LoggerInterface)Class.forName("com.infosys.setl.utils.SETLogger").newInstance();
	}
	catch (Exception e)
	{
	    e.printStackTrace();
	}
	finally
	{
		return l;
	}
  }	
}
package com.infosys.setl.utils;
public class SETLogger implements LoggerInterface
{
  public void log(String logMsg, Object target)
  {
	System.out.println(logMsg);
  }
}
package com.infosys.setl.aspects;
import com.infosys.setl.utils.*;
public aspect UnplugAspect
{
  void around(String logMsg, Object source):
  call (void java.io.PrintStream.println(String))
   && within(com.infosys.setl.apps.AppA.ClassA)
   && !within(com.infosys.setl.aspects..*)
   && args(logMsg)
   && target(source)
  {
	logger.log(logMsg, source);
  }	
  private LoggerInterface logger = LoggerFactory.getInstance().getLogger(); 
}

動的分析

このセクションでは、もう一度、動的分析を一般的なメンテナンス・シナリオに適用させながら、動的分析のいくつかのテクニックについて振り返ります。動的分析を使って解決するシナリオは、以下のとおりです。

  • 動的な呼び出しグラフの生成
  • システムに関する例外の影響の評価
  • 特定の条件に基づいたシステムのカスタマイズ

アプリケーションコードベースをローカル環境へチェックアウトする必要がある動的なAOP分析を再呼び出しします。そして、AspectJを使うためにビルドファイルを変更し、適切な場所でaspectクラスをインクルードします。その後、システム全体をビルドし、システムが設計されたユースケースを実行します。正しく、interestのjoin pointを指定したとすれば、リフレクションによる豊富な情報は実行中のアプリケーションに収集されるでしょう。

動的なグラフ呼び出しの生成

常に、単一のユースケースは複数のプログラム・モジュールをトラバースします。予想したコードの実行順序で実行し、このトラバーサルパスのメンタルな表現を構成するのは、珍しくありません。明らかに、この全ての情報を思い出すことは、(より大規模なシステムで常に生じるので)トラバーサルパスが長くなると同時に、より困難になります。このセクションでは、ユースケースを最初から最後まで実行すると、実行スタックでプッシングやポッピングのフレームを描く、入れ子になったトレースの動的なグラフ呼び出しを生成する方法が分かります。

リスト5では、ユースケースの実行に従って、動的なグラフ呼び出しを生成するために、どのようにコンパイル時のaspectを使用すればよいのかが分かります。

リスト5.制御フロー グラフ生成
package com.infosys.abhi;
public class ClassA
{
  public void methodA(int x)
  {
	x*=2;
	com.infosys.abhi.ClassB b = new com.infosys.abhi.ClassB();
	b.methodB(x);
  }
}
package com.infosys.abhi;
public class ClassB
{
  public void methodB(int x)
  {
	x*=2;
	com.infosys.bela.ClassC c = new com.infosys.bela.ClassC();
	c.methodC(x);
  }
}
package com.infosys.bela;
public class ClassC
{
  public void methodC(int x)
  {
  	x*=2;
	com.infosys.bela.ClassD d = new com.infosys.bela.ClassD();
	d.methodD(x);
  }
}
package com.infosys.bela;
public class ClassD
{
  public void methodD(int x)
  {
	x*=2;
  }
}
package com.infosys.setl.aspects;
public aspect LogStackAspect
{
  private int callDepth=0;
  pointcut cgraph(): !within(com.infosys.setl.aspects..*)
   && execution(* com.infosys..*.*(..));
  Object around(): cgraph()
  {
    callDepth++;
    logEntry(thisjoin point, callDepth);
    Object result = proceed();
    callDepth--;
    logExit(thisjoin point, callDepth);
    return result;
  }
	
  void logEntry(join point jp, int callDepth)
  {
	StringBuffer msgBuff = new StringBuffer();
	while (callDepth >0)
	{
		msgBuff.append(" ");
		callDepth--;	
	}
	msgBuff.append("->").append(jp.toString());
	log(msgBuff.toString());
  }
	
  void logExit(join point jp, int callDepth)
  {
	StringBuffer msgBuff = new StringBuffer();
	while (callDepth >0)
	{
		msgBuff.append(" ");
		callDepth--;	
	}
	msgBuff.append("<-").append(jp.toString());
	log(msgBuff.toString());
  }
	
  void log(String msg)
  {
 	System.out.println(msg);
  }
}
Output:
=======
->execution(void com.infosys.abhi.ClassA.methodA(int))
  ->execution(void com.infosys.abhi.ClassB.methodB(int))
   ->execution(void com.infosys.bela.ClassC.methodC(int))
    ->execution(void com.infosys.bela.ClassD.methodD(int))
   <-execution(void com.infosys.bela.ClassD.methodD(int))
  <-execution(void com.infosys.bela.ClassC.methodC(int))
 <-execution(void com.infosys.abhi.ClassB.methodB(int))
<-execution(void com.infosys.abhi.ClassA.methodA(int))

AspectJはあるpoint cutによって選ばれたそれぞれのjoin pointの制御フローの下で、join point(実行ポイント) を示すcflowbelowと呼ばれる構成を提供しています。この構造を使用すると、任意の上下幅を切り捨てるために、グラフ呼び出しを求めることができます。これは、同じグラフ出力の呼び出しが何度も繰り返され(次々にグラフサイズが増加し、結果として有用性の低下に結びつきます)、制御フローが大きなループを走行しているような状況で役に立ちます。

例外の影響の評価

しばしば、本番環境に生じた例外をデバッグするように要求されることもあるでしょう。そのような例外は、例えばユーザ・インターフェース上でスタックトレースをダンプしたり、共用ロックのような問題のリソースを解放しないなどの望ましくない結果を引き起こす可能性があります。

システム全体に関する例外の影響を評価することは重要ですが、あなたの開発環境である例外をシミュレートすることは非常に困難かもしれません。これは、例外の性質(ネットワーク例外のような)、あるいは本番環境と開発環境が全く同じではないのが理由でしょう。例えば、データベース破壊により本番環境で生じた例外は、破壊の正確な場所を知ることなしではシュミレートすることができません。さらに、開発サーバーへ転送するために本番のデータベースのスナップショットを取り込むことは可能ではないかもしれません。

アプリケーション内で問題となっている行を理解するための例外シミュレートに加え、正しくこの問題を処理するかを確かめるために修正コードのテストをするでしょう。パッチしたコードでの例外の生成やその処理を監視しないのであれば、これは問題です。このセクションでは、例外が実際に投げられるであろう正確な実行条件を再作成しないで、オブジェクト上でのメソッド呼び出しの結果として、例外のスローをシミュレートするために、どのようにAOP技術を使用すればよいのか分かるでしょう。

リスト6で示すサンプルクラスのcom.infosys.setl.apps.AppA.ClassAをよく見て下さい。このクラスのmethodA()への呼び出しは、ある状況においてjava.io.IOExceptionが投げられます。もしこの例外が起った場合に、methodA()の呼び出し(同リストで示すcom.infosys.setl.apps.AppB.ClassB)の振る舞いを調べようとします。しかしながら、実際の例外を発生させる条件をセット・アップために、リソースや時間に手間をかけたくありません。

これを解決するために、実行時にmethodA()の実行をトラップし、java.io.IOExceptionを発生させるために、aspectGenExceptionのpointcutgenExceptionAを使用します。そして、リスト6で示すように、アプリケーション(この場合、ClassB)が記述ごとに例外を処理できるかどうかをテストすることができます。(もちろん、pointcutgenExceptionAfterAで表わされるように、adviceをmethodAの実行に続いて実行することができる“after”adviceに変更できます。)

実際の開発シナリオでは、ClassAがJDBCドライバのようなサード・パーティーライブラリーかもしれないことに注意してください。

リスト6: 実行コードからの例外の生成
package com.infosys.abhi;
import java.io.*;
public class ClassA
{
  public void methodA() throws IOException
  {
	System.out.println("Hello, World!");
  }
}
package com.infosys.bela;
public class ClassB
{
  public static void main(String[] args)
  {
	try
	{
		com.infosys.abhi.ClassA a = new com.infosys.abhi.ClassA();
		a.methodA();
		com.infosys.abhi.ClassC c = new com.infosys.abhi.ClassC();
		System.out.println(c.methodC());		
	}
	catch (java.io.IOException e)
	{
		e.printStackTrace();
	}
	catch (Exception e)
	{
		e.printStackTrace();
	}
  }
}
package com.infosys.abhi;
public class ClassC
{
  public String methodC()
  {
	System.out.println("Hello, World!");
	return "Hi, World!";
  }
}
package com.infosys.setl.aspects;
public aspect GenException
{
  pointcut genExceptionA(): execution(public void com.
infosys.abhi.ClassA.methodA() throws java.io.IOException);
  pointcut genExceptionC(): call(void java.io.PrintStream.println(String))
&& withincode(public String com.infosys.abhi.ClassC.methodC());
  pointcut genExceptionAfterA(): call(public void com.
infosys.abhi.ClassA.methodA() throws java.io.IOException);
	
  void around() throws java.io.IOException : genExceptionA()
  {
	java.io.IOException e = new java.io.IOException();
	throw e;
  }
  after() throws java.io.IOException : genExceptionAfterA()
  {
	java.io.IOException e = new java.io.IOException();
	throw e;
  }
	
  after() throws java.lang.OutOfMemoryError : genExceptionC()
  {
	java.lang.OutOfMemoryError e = new java.lang.OutOfMemoryError();
	throw e;
  }
}

同様のシナリオでは、チェックなし例外(NullPointerExceptionのようなjava.lang.RuntimeExceptionのサブクラス)あるいはエラー(OutOfMemoryErrorのようなjava.lang.Errorのサブクラス)を投げるアプリケーションに出会うかもしれません。これらの例外タイプは両方とも、開発環境でシミュレートすることが困難です。代わりに、実行コードにClassCmethodC()内でSystem.out.println()の呼び出しに続くOutOfMemoryエラーを投げさせるために、対応するafter advice(上に示す)と一緒にpointcutgenExceptionCを使用することができます。

条件に基づいたシステムのカスタマイズ

一般的には、AOPは多数のシステム・モジュールを横断するconcernのコンテキストにあると考えられています。しなしながら、それが、システムの特定の部分のみへ集中するconcernのためにaspectを使用する機会です。例えば、顧客の要望に答えたり、ローカルで規定する法則の変更を反映するなど、システムにいくつかの非常に独特な変更を加えることを要求されるかもしれません。ある変更は一時的なものかもしれません。(すなわち、特定の期間が過ぎた後、それらをシステムから取り除かなければなりません)。

そのような場合、以下の理由により、関連する機能拡張を直接コード内で行うのに、コードを分岐させるのが可能(あるいは望ましい)だと分からないかもしれません。

  • 異なったバーションがあるCVSで、複数のコード分岐の管理の必要性が発生してしまうかもしれません。これは、管理する上で悩みの種となり、エラー発生の可能性を増加させてしまします。たとえ全ての要求された拡張が、製品のロードマップ全体を考慮にいれる有益な機能だとしても、aspectによりこれらの機能を導入することは、最初に開発チームに、(いわば、CVSへの変更をコミットする必要なしで)「様子を見る」機会を与えるでしょう。
  • 一時的な機能がシステムから取り除かれる場合、レグレッション・テストが必要となり、機能がコードに直接導入されていた場合は、リソースを消費し、間違いを起こしやすくなるでしょう。一方、もし機能がAOPによって導入されたと仮定するならば、除去はせいぜいbuildファイルからaspectを除外してシステムを再コンパイルするだけになり、シンプルです。

幸運にも、そのようなカスタマイズにおいて、aspectによりシステムに織り込むことは容易です。この織り込みにより、コンパイル時(AspectJで)又はロード時(AspectWerkzで)に組み込まれます。aspectを設計する場合、AOPインプリメンテーションがコンテキスト公開に関して、固有の制限を持っているかもしれないと心に留めておくことは重要です。(例えば、AspectJは、メソッドのローカル変数が実行コンテキストによってadviceコードへ公開されるのを許可しません。)しかしながら、一般的に言えば、システムをカスタマイズするのにaspectを使用することは、システムコードの基本的なバージョンから分離されたconcernを持つ簡潔な実装に結びつくでしょう。


結論

この記事では、大きく複雑なレガシーシステムを理解し、控えめに保守をするためにアスペクト指向プログラミングを使用する方法を学習しました。エラーの解決と控えめな既存コードの拡張に重点を置いて、いくつかの一般的なメンテナンス・シナリオへdynamic crosscutting (動的な横断)のパワーと2つの分析の技術を組み合わせました。レガシーアプリケーションを日々メンテナンスする責任者である場合、ここで実証した全てのテクニックは非常に貴重であると分かるでしょう。

参考文献

  • AspectJ Project homepageからAspectJをダウンロードすることができます。
  • AOPとAspectJに関する詳細な入門については「アスペクト指向プログラミングで、モジュール性を改善する」(developerWorks、2002年1月)を参照して下さい。
  • AspectJおよび疑似オブジェクトによる柔軟なテスト」(developerWorks、2002年5月)を参照し、JavaベースのAOPのよく知られているパターンについて更に学習してください。
  • AOPが密結合の憂うつさを取り除く」(developerWorks、2004年2月)は、static crosscutting(静的な横断)技術への実践入門です。
  • AspectWerkzは軽量なJavaベースのAOPインプリメンテーションのオープンソースです。
  • Rigiはソフトウェアの理解促進と再ドキュメント化を支援する目的で設計されたインタラクティブな視覚的ツールです。(カナダのブリティッシュ・コロンビア州にあるビクトリア大学のコンピューター学科の研究者によって開発されました。)
  • Klocwork InSightはアプリケーションの構造と設計の包括的な理解を提供するために、既存のソースコード(C、C++やJava)から直接ソフトウェア設計の正確なグラフィカルな分析結果を出力します。
  • developerWorks のJava technology zoneには、Javaプログラミングのあらゆる側面に関する記事があります。
  • Javaに関連する何百ものタイトルを含む技術本の包括的なリストのあるDeveloper Bookstoreを訪れてみて下さい。
  • さらに、developerWorksからの無償で提供されているJavaに焦点を置いたチュートリアルのリストはJava technology zone tutorials pageにあります。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218842
ArticleTitle=レガシーJavaアプリケーションの保守にAOPを使用する
publish-date=03092004