请注意:我将在这里回答我自己的问题,为 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
化为工作对象,设置其状态
- 实现该方法,可选择使用通过or
log()
绑定的连接点状态,this()
args()
- 调用/运行工作对象(调用将被日志记录建议的切入点拦截,并在那里完成真正的日志记录业务)。