0

我的情况如下:我有LoggingAspect几个切入点与我的主应用程序中的特定方法执行相匹配。对应的advice body基本都长得差不多,造成了很多代码重复:

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

不过,有一些变化。有时切入点和建议有一个argorthis参数,该参数也打印到日志中。有时,如果“完成”消息只是一个小调用,没有包含很多其他调用,则不会打印出来,如下所示:

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

建议将proceed() 包装在一个匿名类中。并编写一个处理此执行的方面(但不要忘记proceed()的潜在异常)。

我的建议:

// 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 回答
2

请注意:我将在这里回答我自己的问题,为 loddar2012 建议的解决方案添加更多信息和参数化的附加功能。因为他的回答将我引向了正确的方向,所以我会接受它,即使这里的答案确实解决了我对原始问题的所有需求,例如(引用我自己):

不过,有一些变化。有时切入点和建议有一个 arg 或这个参数,它也会打印到日志中。有时,如果它只是一个小调用而不包含许多其他调用,则不会打印“完成”消息

我们在这里处理的基本内容是 Ramnivas Laddad在他的AspectJ in Action一书中所说的工作对象模式。他(和 loddar2012 的)想法是,用简单的散文

  • 将调用包装到匿名类(工作对象)的实例中,其中
    • 基类或实现的接口提供了用于完成工作的方法,
    • worker 对象提供了 worker 方法的具体实现,并在proceed()其中具体调用,
    • worker 方法可以在对象创建后立即调用(正如我们将在此处执行的那样)或稍后,甚至可以在它自己的线程中调用,
    • worker 对象可以被传递或添加到调度队列中(我们在这里都不需要)。

如果您需要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()方法)和之后做一些事情,使用存储在工作对象内部的状态信息,例如

  • 要记录的消息,
  • 日志级别(这里称为“类型”),
  • 指定在继续之前是否应提高缩进级别的标志,
  • 指定是否应在工作人员执行后打印“完成”消息的标志。

因为在我的用例中,所有记录的方法都返回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化为工作对象,设置其状态
  • 实现该方法,可选择使用通过orlog()绑定的连接点状态,this()args()
  • 调用/运行工作对象(调用将被日志记录建议的切入点拦截,并在那里完成真正的日志记录业务)。
于 2012-08-26T14:15:36.647 回答