6

私はaspectjの初心者です...

type の関数呼び出しにロギングを追加することを目的とした次のアスペクトを作成しましたpublic * doSomething*(..)。メイン クラスが同じプロジェクトの一部である場合、アスペクトのウィービングは問題なく実行され、コードが実行されます。織り込まれたコードを jar にまとめて別の Eclipse プロジェクトから呼び出すと、アドバイスは実行されません。別のシナリオでは、アスペクト (.aj) のみを別の jar にパックし、その jar を Eclipse の「アスペクト パス」に追加します。これにより、Eclipse はアスペクトを適切に織り込むことができます。問題は、これを jar にラップして、別の場所からコードを呼び出す必要があることです。それも機能しません (驚くべきことではないと思います...) なぜですか?

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.apache.log4j.Logger;

public aspect Logging {
    pointcut allPublic(): !cflow(call(public void main(..))) && (call(public * doSomething*(..)));

    private static final Logger log = Logger.getLogger("Logging.aspect");

    @SuppressWarnings({"unchecked", "unused"})
    private void printParameters(JoinPoint jp) {
        CodeSignature methodSignature = (CodeSignature) jp.getSignature();
        String methodName = methodSignature.getName();
        Object[] paramNames = methodSignature.getParameterNames();
        Class[] paramTypes = (Class[])methodSignature.getParameterTypes();
        Object[] paramObjects = jp.getArgs();
        StringBuffer infoMsg = new StringBuffer();

        infoMsg.append("Entering function: " + methodName);
        if (paramNames != null && paramNames.length > 0){
            if (paramNames.length == 1){
                infoMsg.append(" with input parameter: ["+ paramNames[1]+ "] = [" + paramObjects[1] + "]");
            }
            else {
                infoMsg.append(" with input parameters: ");
            }
            for (int i = 1; i < paramNames.length; i++) {
                infoMsg.append(" [" + paramTypes[i].getName() + " " + paramNames[i]+ "] = [" + paramObjects[i] + "]");
            }
        }
        else {
            infoMsg.append(" NONE");
        }
       log.info(infoMsg.toString());

    }

    @SuppressWarnings("unused")
    private void printExit(JoinPoint jp) {
        log.info("Exit function: " + jp.getSignature().toString());
    }

    before() : allPublic() {
        printParameters (thisJoinPoint);
    }

    after() : allPublic() {
        printExit(thisJoinPoint);
    }
}

アドバイスされるはずのクラス:

public class Main {

    private static final Logger log = Logger.getLogger("A.class");

    public static void doSomethingAa(int number, String message, Map<String, String> map){
        log.debug("A");
    } 

    public static void doSomethingB(int id, String name){
        log.debug("B");
    }

    public static void main(String[] args){
        Map<String, String> map1 = new TreeMap<String, String>();
        Map<String, String> map2 = new TreeMap<String, String>();

        map1.put("FirstKey", "FirstValue");
        map1.put("SecondKey", "SecondValue");

        map2.put("Tal", "Guy");
        map2.put("Happy", "Birthday");

        A.doSomethingAa(17, "Tal", map1);
        A.doSomethingAa(35, "Guy", map2); 

        A.doSomethingB(12, "TalG");
        A.doSomethingB(40, "GuyG");

        System.out.println("Finished running main");

    }

}

皆さんありがとう!

4

1 に答える 1

9

私はプラグイン開発でspectjを使用しようとしたことがないので、いくつかの追加事項があるかもしれません. ただし、ターゲットがコンパイル時に正しく織り込まれ、実行可能であることを確認するために必要なことがいくつかあります。

  • 織り込まれているプラ​​グインは、アスペクトを含むプラグインに依存する必要があります
  • アスペクトは、クラスパスのエクスポートされたパッケージにある必要があります
  • ターゲット プラグインは、アスペクトを処理できるように、クラスパスにspectjrtが必要です。
  • コンパイル時にターゲットを織り込むには、aspectj コンパイラを使用する必要があります。

更新、問題を再現できませんでした (つまり、私のボックスでは正常に動作します)。この状況を再現するために、ソース ディレクトリに 1 つの Logging.aj ファイルを含む AspectJ プロジェクトを作成しました。それをjarファイル(logging.jarと呼ばれる)として別のプロジェクトのルートにエクスポートしました(別のプロジェクトも「メイン」クラスを含むAspectJプロジェクトとして設定されています)。次に、「メイン」プロジェクトのアスペクト パスを変更して、logging.jar とアスペクトを含め、各 doSomethingAa() および doSomethingB() メソッド呼び出しにアドバイスを織り込みました。

私があなたのコードで見つけた唯一の問題は、静的メソッドの呼び出しが「メイン」ではなく「A」であることです。

メイン プロジェクトの .classpath ファイルのエントリは次のとおりです。

<classpathentry kind="lib" path="logging.jar">
  <attributes>
    <attribute name="org.eclipse.ajdt.aspectpath"
        value="org.eclipse.ajdt.aspectpath"/>
  </attributes>
</classpathentry>

さまざまな順列を試しましたが、機能しないようにする唯一の方法は、AspectJ の性質を削除するか、ビルド パスから jar を削除することです。

ワークスペースに影響を与えている可能性のある要因のうち、除外したものは他にありますか?


同様のプロジェクトで見つけたロギングの側面に関するもう1つのポイント。前後のアドバイスを別々にすると、メソッド呼び出しごとに JoinPoint インスタンスが 2 回作成されます。これにより、ロギング タイプが多くのメソッドを織り込んでいる場合、ガベージ コレクションで問題が発生する可能性があります。代わりに、アラウンドアドバイスを使用してエントリと終了の両方をログに記録することを検討できます。これにより、後で決定した場合にメソッド実行時間のログを簡単に追加できます。


更新: コメントに基づいて、3 番目のプロジェクト (aj_client) をワークスペースに追加し、次の手順を実行しました。

  1. System.out 呼び出しを行うように Logging.aj を変更し、log4j 構成の問題を排除しました。
    • aj_logging (Logging.aj を含む AspectJ プロジェクト) を logging.jar にエクスポート
    • aj_target の Aspect Path に logging.jar を追加
    • aj_target (Main.java を含む AspectJ プロジェクト) を target.jar にエクスポート
    • aj_client プロジェクト (AspectJ の性質を持たない) に新しいクラス (Client.java) を作成しました。
    • aj_client の Java ビルド パスに target.jar、logging.jar (および log4j.jar) を追加して実行しました。

Client.java には次の 1 つのメソッドが含まれています。

public static void main(String[] args) {
    Main.main(args);
}

実行すると、これは NoClassDefFoundError で失敗します。

Exception in thread "main" java.lang.NoClassDefFoundError: org/aspectj/lang/Signature
at Client.main(Client.java:6)
Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.Signature

これに対処するために、aj_client の .classpath を変更して、( AspectJ ランタイム ライブラリクラスパス コンテナーを .classpath に手動で追加することにより) アスペクトjrt が含まれるように変更し、再実行すると、プログラムが実行され、ログ ステートメントが出力されます。

Entering function: doSomethingAa with input parameters:  [java.lang.String message] = [Tal] [java.util.Map map] = [{FirstKey=FirstValue, SecondKey=SecondValue}]
log4j:WARN No appenders could be found for logger (A.class).
log4j:WARN Please initialize the log4j system properly.
Exit function: void target.Main.doSomethingAa(int, String, Map)
Entering function: doSomethingAa with input parameters:  [java.lang.String message] = [Guy] [java.util.Map map] = [{Happy=Birthday, Tal=Guy}]
Exit function: void target.Main.doSomethingAa(int, String, Map)
Entering function: doSomethingB with input parameters:  [java.lang.String name] = [TalG]
Exit function: void target.Main.doSomethingB(int, String)
Entering function: doSomethingB with input parameters:  [java.lang.String name] = [GuyG]
Exit function: void target.Main.doSomethingB(int, String)
Finished running main

aj_client の .classpath ファイルは次のようになります。

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
    <classpathentry kind="src" path="src/main/java"/>
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
    <classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>
    <!-- the other jars for the logging and target projects -->
    <classpathentry kind="lib" path="/aj_target/target.jar"/>
    <classpathentry kind="lib" path="/aj_target/log4j-1.2.14.jar"/>
    <classpathentry kind="lib" path="/aj_target/logging.jar"/>
    <classpathentry kind="output" path="target/classes"/>
</classpath>

また、Maven リポジトリと Eclipse プラグインで自分のspectjrt を指定しようとしましたが、同じ結果になりました (ログメッセージが出力されました)。つまり、次のように置き換えます。

<classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>

<!--aspectjrt from Maven repository-->
<classpathentry kind="lib" path="C:/maven-2.2.0/repo/aspectj/aspectjrt/1.5.3/aspectjrt-1.5.3.jar"/>

また

<!--aspectjrt from Eclipse plugin -->
<classpathentry kind="lib" path="C:/eclipse-3.5/eclipse/plugins/org.aspectj.runtime_1.6.5.20090618034232/aspectjrt.jar"/>

ロギング コードが織り込まれていることが証明されたので、再度 getLog().info() 呼び出しを使用するように Logging.aj を変更したところ、ロギング ステートメントが出力されなくなっていることがわかりました。これを改善するために、log4j.xml 構成ファイルを追加しました (ルート アペンダーを指定するだけです)。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender"> 
    <param name="Target" value="System.out"/> 
    <layout class="org.apache.log4j.PatternLayout"> 
      <param name="ConversionPattern" value="%-5p %c{1} - %m%n"/> 
    </layout> 
  </appender> 

  <root> 
    <priority value ="debug" /> 
    <appender-ref ref="console" /> 
  </root>

</log4j:configuration>

これにより、次の出力が得られました。

DEBUG class - A
INFO  Logging - Exit function: void target.Main.doSomethingAa(int, String, Map)
INFO  Logging - Entering function: doSomethingB with input parameters:  [java.lang.String name] = [TalG]
DEBUG class - B
INFO  Logging - Exit function: void target.Main.doSomethingB(int, String)
INFO  Logging - Entering function: doSomethingB with input parameters:  [java.lang.String name] = [GuyG]
DEBUG class - B
INFO  Logging - Exit function: void target.Main.doSomethingB(int, String)
Finished running main

注 target.jar をクリーニング、ビルド、およびエクスポートする前に、logging.jar をクリーニング、ビルド、およびエクスポートしたことを確認してから、クライアント プロジェクトをクリーニングするように注意する必要があります。順序を少しでも間違えると、内容が一致しなくなります。


概要

したがって、クライアント プロジェクトが AspectJ でビルドされた「target.jar」を参照している限り (Logging.aj が織り込まれている)、クラスパスaspectjrt.jar があり、log4j を正しく構成している限り、ログは表示されます。出力されます。

クラスパスコンテナを追加するか、互換性のあるaspectjrt.jarへのパスを指定することにより、aspectjrt依存関係を指定できます

于 2009-08-12T11:08:29.643 に答える