请注意:我将在这里回答我自己的问题,为 loddar2012 建议的解决方案添加更多信息和参数化的附加功能。因为他的回答将我引向了正确的方向,所以我会接受它,即使这里的回答确实满足了我对原始问题的所有需求,例如(引用自己的话):
不过还是有一些变化。有时切入点和建议有一个 arg 或这个参数,它也会打印到日志中。有时,如果它只是一个小调用而不包含许多其他调用,则不会打印“完成”消息
我们在这里处理的基本问题是 Ramnivas Laddad 在他的书 AspectJ in Action 中称之为 worker 对象模式。他(和 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)之前做了一些事情(即执行worker对象的log()方法)和之后的一些事情,使用存储在worker对象中的状态信息,例如
- 要记录的消息,
- 日志级别(这里称为“类型”),
- 标志指定在继续之前是否应提高缩进级别,
- 标志指定是否应在工作人员执行后打印“完成”消息。
因为在我的用例中,所有记录的方法都返回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 实例化为具有一个或多个构造函数参数的工作对象,并设置其状态
- 实现
log() 方法,可选择使用通过this() 或args() 绑定的连接点状态,
- 调用/运行工作对象(调用将被日志建议的切入点拦截,并在那里完成真正的日志业务)。