我的情况如下:我有一个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()
切入点。
答案 0 :(得分: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;
}
};
最好的问候Marko
答案 1 :(得分:2)
请注意:我将在此回答我自己的问题,将更多信息和参数化的附加功能添加到loddar2012建议的解决方案中。因为他的回答让我朝着正确的方向前进,所以我会接受它,即使这里的答案真正满足了原始问题的所有需求,例如(引用自己):
但是有一些变化。有时候切入点和advice有一个arg或this参数,它也会打印到日志中。有时,如果只是一个小调用而不包含很多其他调用,则不会打印“完成”消息
我们在这里处理的基本事情是Ramnivas Laddad在他的书AspectJ in Action中称之为工人对象模式的内容。他的(和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)
(即执行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()
绑定的连接点状态,