0

次のような状況がありLoggingAspectます。メインアプリケーションの特定のメソッド実行に一致するいくつかのポイントカットがあります。対応するアドバイス機関は基本的にすべて同じように見え、多くのコードの重複を引き起こします。

void around() : download() {
    String message = "Downloading, verifying (MD5) and unpacking";
    SimpleLogger.verbose(message, IndentMode.INDENT_AFTER);
    proceed();
    SimpleLogger.verbose(message + " - done", IndentMode.DEDENT_BEFORE);
}

ただし、いくつかのバリエーションがあります。ポイントカットとアドバイスには、ログにも出力されるパラメータが含まれているarg場合があります。this次のように、他の多くの呼び出しをラップしていないマイナーな呼び出しである場合、「完了」メッセージが出力されないことがあります。

void around(BasicFilter filter) : fixFaultyLinkTargets()  && this(filter) {
    String message = "TOC file: checking for faulty link targets";
    SimpleLogger.verbose(message, IndentMode.INDENT_AFTER);
    proceed(filter);
    SimpleLogger.dedent();
}

いつものことは、私が手動でロガーに伝えることです

  • proceed()最初のメッセージが出力された後、つまり呼び出される直前にインデントレベルを上げるには、
  • proceed()最終メッセージが印刷される前(印刷されている場合)、つまり戻った直後にインデントレベルを下げる。

私の考えは、インデントレベルを自動的に調整するために、proceed()呼び出しをインターセプトするポイントカットを使用してメタアスペクト(またはヘルパーアスペクトと呼びます)を記述したいと考えています。LoggingAspectしかし、ポイントカットマッチングはないようproceed()です。ロギングの面ですべてに一致するポイントカットでさえも試しcall(SomeMethodInMyMainApp)ましたが、ポイントカットは必要のないものに一致しますが、続行することはありません。

誰かが私がこれを行う方法を知っているなら、私はヒントまたはコードスニペットをいただければ幸いです。

これを行う間接的な方法は、アドバイス自体ではなく、次のような追加のポイントカットを作成することによって、アドバイスによってアドバイスされたメソッド呼び出し(または実行)をインターセプトすることです。

// ATTENTION: each new pointcut must also be added here
pointcut catchAll() : download() || fixFaultyLinkTargets() || ...;

void around() : catchAll() {
    SimpleLogger.indent();
    proceed();
    SimpleLogger.dedent();
}

catchAll()ただし、ロギングの側面で何かを変更するたびに余分なポイントカットを更新することを覚えておく必要がないので、別の方法をお勧めします。

4

2 に答える 2

2

注:ここで私自身の質問に答え、loddar2012によって提案されたソリューションにさらに情報とパラメーター化の追加機能を追加します。彼の答えが私を正しい方向に導いたので、ここでのこの答えは、(自分自身を引用して)など、元の質問からの私のすべてのニーズに本当に対応しているにもかかわらず、私はそれを受け入れるつもりです。

ただし、いくつかのバリエーションがあります。ポイントカットとアドバイスには、ログに出力されるargまたはこのパラメーターが含まれる場合があります。他の多くの呼び出しをラップしていない単なるマイナーな呼び出しである場合、「完了」メッセージが出力されないことがあります

ここで扱っている基本的なことは、ラムニヴァス・ラダッドが彼の著書AspectJinActionでワーカーオブジェクトパターンと呼んでいるものです。彼の(そしてloddar2012の)アイデアは、単純な散文です

  • 呼び出しを匿名クラス(ワーカーオブジェクト)のインスタンスにラップするには、
    • 基本クラスまたは実装されたインターフェースは、作業を行うことを目的としたメソッドを提供します。
    • ワーカーオブジェクトは、ワーカーメソッドの具体的な実装を提供し、具体的にはproceed()その中で呼び出します。
    • ワーカーメソッドは、オブジェクトの作成直後(ここで行うように)または後で、場合によっては独自のスレッドで呼び出すことができます。
    • ワーカーオブジェクトは、渡されたり、スケジューリングキューに追加されたりする場合があります(ここでは必要ありません)。

proceed()呼び出しを非同期で実行する必要がある場合の洗練されたソリューションは、匿名Runnableクラスのインスタンスを作成することです。ただし、お茶に砂糖をもう少し入れたいので、独自の抽象基本クラスを使用LogHelperします。具体的には、ログメッセージと、各ワーカーへのログ出力に影響を与えるその他のパラメーターを渡すオプションです。だからこれは私がしたことです(パッケージ名とインポートはサンプルコードに示されていません):

抽象ワーカー基本クラス:

abstract class LogHelper {
    // Object state needed for logging
    String message;
    boolean logDone;
    boolean indent;
    LogType type;

    // Main constructor
    LogHelper(String message, boolean logDone, boolean indent, LogType type) {
        this.message = message;
        this.logDone = logDone;
        this.indent = indent;
        this.type = type;
    }
    // Convenience constructors for frequent use cases
    LogHelper(String message, boolean logDone) {
        this(message, logDone, true, LogType.VERBOSE);
    }
    LogHelper(String message) {
        this(message, true);
    }

    // Worker method to be overridden by each anonymous subclass
    abstract void log();
}

ワーカーオブジェクトの実行をキャプチャするログアドバイス:

aspect LoggingAspect
{
    void around(LogHelper logHelper) :
        execution(* LogHelper.log()) && this(logHelper)
    {
        try {
            SimpleLogger.log(logHelper.type, logHelper.message);
            if (logHelper.indent)
                SimpleLogger.indent();
            proceed(logHelper);
        } finally {
            if (logHelper.indent)
                SimpleLogger.dedent();
            if (logHelper.logDone)
                SimpleLogger.log(logHelper.type, logHelper.message + " - done");
        }
    }
    // (...)
}

ご覧のとおり、ロギングアドバイスは、呼び出し前proceed(logHelper)(つまり、ワーカーオブジェクトのlog()メソッドの実行)にいくつかのことを行い、その後、ワーカーオブジェクト内に格納されている状態情報を使用していくつかのことを行います。

  • ログに記録されるメッセージ、
  • ログレベル(ここでは「タイプ」と呼ばれます)、
  • 続行する前にインデントレベルを上げる必要があるかどうかを指定するフラグ、
  • ワーカーの実行後に「done」メッセージを出力するかどうかを指定するフラグ。

私のユースケースでは、ログに記録されたすべてのメソッドが返されるため、void戻り値の受け渡しを実装する必要はありませんが、必要に応じて簡単に実行できます。その場合、アドバイスの戻り値はそのままになりObject、結果をproceed()呼び出し元に返します。大したことはありません。

ログに記録される参加ポイントをキャプチャし、パラメータ化されたワーカーオブジェクトを利用して作業を完了するためのアドバイス:

aspect LoggingAspect
{
    // (...)

    pointcut processBook()     : execution(* OpenbookCleaner.downloadAndCleanBook(Book));
    pointcut download()        : execution(* Downloader.download());
    pointcut cleanBook()       : execution(* OpenbookCleaner.cleanBook(Book));
    pointcut cleanChapter()    : execution(* OpenbookCleaner.cleanChapter(Book, File));
    pointcut initialiseTitle() : execution(* *Filter.initialiseTitle(boolean));

    void around(final Book book) : processBook() && args(book) {
        new LogHelper("Book: " + book.unpackDirectory) {
            void log() { proceed(book); } }.log();
    }
    void around() : download() {
        new LogHelper("Downloading, verifying (MD5) and unpacking") {
            void log() { proceed(); } }.log();
    }
    void around() : cleanBook() {
        new LogHelper("Filtering") {
            void log() { proceed(); } }.log();
    }
    void around(final File origFile) : cleanChapter() && args(*, origFile) {
        new LogHelper("Chapter: " + origFile.getName()) {
            void log() { proceed(origFile); } }.log();
    }
    void around() : initialiseTitle() {
        new LogHelper("Initialising page title", false) {
            void log() { proceed(); } }.log();
    }
}

例はあなたができる方法を示しています

  • 匿名LogHelperを1つ以上のコンストラクターパラメーターを使用してワーカーオブジェクトとしてインスタンス化し、その状態を設定します
  • またはをlog()介してバインドされたジョインポイント状態をオプションで使用して、メソッドを実装します。this()args()
  • ワーカーオブジェクトを呼び出す/実行します(呼び出しはロギングアドバイスのポイントカットによってインターセプトされ、実際のロギングビジネスはそこで行われます)。
于 2012-08-26T14:15:36.647 に答える
2

提案は、proced() を匿名クラスでラップします。そして、この実行に対処する側面を書きます (ただし、proced() の潜在的な例外を忘れないでください)。

私のおすすめ:

// AspectProceedCaller.java
public abstract class AspectProceedCaller { 
    public abstract Object doProceed(); 
};

// aspect ProceedCallerAspect.aj
aspect ProceedCallerAspect {
     pointcut execProceedCaller() : execution( * AspectProceedCaller+.doProceed() );

     Object around() : execProceedCaller() {
         try {
              SimpleLogger.indent();
              return proceed();
         }
         finally {
              SimpleLogger.dedent();
         }
     }
};


// Your application aspect 
aspect AnyAspect {
    pointcut anyPointcut() : ...;

    Object around() : anyPointcut() {
        AspectProceedCaller apc=new AspectProceedCaller() {
            public Object doProceed() {
                return proceed();
            }
        };  

        // DO Stuff before ....

        Object retval = apc.doProceed();

        // ... and after calling proceed.

        return retval;
    }
};

よろしくマルコ

于 2012-08-25T12:37:28.573 に答える