各种切入点表达式作用域意外触发多个建议调用 [英] Various pointcut expression scopes trigger multiple advice calls unexpectedly
问题描述
使用各个方面记录项目,以使所有标有@Log
注释的方法,类和构造函数均具有写入日志文件的信息.
Logging a project using aspects such that all methods, classes, and constructors that are marked with the @Log
annotation have information written to a log file.
方法似乎被递归地称为一级深度,但是代码未显示任何此类递归关系.
Methods appear to be called recursively one-level deep, but the code does not show any such recursive relationship.
记录的结果:
2018-09-25 12:17:29,155 |↷| EmailNotificationServiceBean#createPayload([SECURE])
2018-09-25 12:17:29,155 |↷| EmailNotificationServiceBean#createPayload([{service.notification.smtp.authentication.password=password, mail.smtp.port=25, service.notification.smtp.authentication.username=dev@localhost, mail.mime.allowutf8=true, mail.smtp.auth=false, mail.smtp.starttls.enable=false, mail.smtp.timeout=10000, mail.smtp.host=localhost}])
2018-09-25 12:17:29,193 |↷| EmailPayloadImpl#<init>([{service.notification.smtp.authentication.password=password, mail.smtp.port=25, service.notification.smtp.authentication.username=dev@localhost, mail.mime.allowutf8=true, mail.smtp.auth=false, mail.smtp.starttls.enable=false, mail.smtp.timeout=10000, mail.smtp.host=localhost}])
2018-09-25 12:17:29,193 |↷| EmailPayloadImpl#validate([SECURE])
2018-09-25 12:17:29,194 |↷| EmailPayloadImpl#validate([{service.notification.smtp.authentication.password=password, mail.smtp.port=25, service.notification.smtp.authentication.username=dev@localhost, mail.mime.allowutf8=true, mail.smtp.auth=false, mail.smtp.starttls.enable=false, mail.smtp.timeout=10000, mail.smtp.host=localhost}, SMTP connection and credentials])
2018-09-25 12:17:29,195 |↷| EmailPayloadImpl#setMailServerSettings([SECURE])
2018-09-25 12:17:29,196 |↷| EmailPayloadImpl#setMailServerSettings([{service.notification.smtp.authentication.password=password, mail.smtp.port=25, service.notification.smtp.authentication.username=dev@localhost, mail.mime.allowutf8=true, mail.smtp.auth=false, mail.smtp.starttls.enable=false, mail.smtp.timeout=10000, mail.smtp.host=localhost}])
预期
预期的记录结果:
Expected
Expected logged results:
2018-09-25 12:17:29,155 |↷| EmailNotificationServiceBean#createPayload([SECURE])
2018-09-25 12:17:29,193 |↷| EmailPayloadImpl#<init>([SECURE])
2018-09-25 12:17:29,193 |↷| EmailPayloadImpl#validate([SECURE])
2018-09-25 12:17:29,195 |↷| EmailPayloadImpl#setMailServerSettings([SECURE])
代码
日志记录方面:
Code
The logging aspect:
@Aspect
public class LogAspect {
@Pointcut("execution(public @Log( secure = true ) *.new(..))")
public void loggedSecureConstructor() { }
@Pointcut("execution(@Log( secure = true ) * *.*(..))")
public void loggedSecureMethod() { }
@Pointcut("execution(public @Log( secure = false ) *.new(..))")
public void loggedConstructor() { }
@Pointcut("execution(@Log( secure = false ) * *.*(..))")
public void loggedMethod() { }
@Pointcut("execution(* (@Log *) .*(..))")
public void loggedClass() { }
@Around("loggedSecureMethod() || loggedSecureConstructor()")
public Object logSecure(final ProceedingJoinPoint joinPoint) throws Throwable {
return log(joinPoint, true);
}
@Around("loggedMethod() || loggedConstructor() || loggedClass()")
public Object log(final ProceedingJoinPoint joinPoint) throws Throwable {
return log(joinPoint, false);
}
private Object log(final ProceedingJoinPoint joinPoint, boolean secure) throws Throwable {
final Signature signature = joinPoint.getSignature();
final Logger log = getLogger(signature);
final String className = getSimpleClassName(signature);
final String memberName = signature.getName();
final Object[] args = joinPoint.getArgs();
final CharSequence indent = getIndentation();
final String params = secure ? "[SECURE]" : Arrays.deepToString(args);
log.trace("\u21B7| {}{}#{}({})", indent, className, memberName, params);
try {
increaseIndent();
return joinPoint.proceed(args);
} catch (final Throwable t) {
final SourceLocation source = joinPoint.getSourceLocation();
log.warn("\u2717| {}[EXCEPTION {}] {}", indent, source, t.getMessage());
throw t;
} finally {
decreaseIndent();
log.trace("\u21B6| {}{}#{}", indent, className, memberName);
}
}
Log
接口定义:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface Log {
boolean secure() default false;
}
反编译的服务bean:
The decompiled service bean:
@Log
public class EmailNotificationServiceBean
implements EmailNotificationService {
@Log(secure = true)
@Override
public EmailPayload createPayload(Map<String, Object> settings) throws NotificationServiceException {
Map<String, Object> map = settings;
JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart)ajc$tjp_2, (Object)this, (Object)this, map);
Object[] arrobject = new Object[]{this, map, joinPoint};
return (EmailPayload)LogAspect.aspectOf().logSecure(new EmailNotificationServiceBean$AjcClosure7(arrobject).linkClosureAndJoinPoint(69648));
}
有效负载实现:
@Log
public class EmailPayloadImpl extends AbstractPayload implements EmailPayload {
@Log(secure = true)
public EmailPayloadImpl(final Map<String, Object> settings)
throws NotificationServiceException {
validate(settings, "SMTP connection and credentials");
setMailServerSettings(settings);
}
@Log(secure = true)
private void validate(final Map<String, Object> map, final String message)
throws NotificationServiceException {
if (map == null || map.isEmpty()) {
throwException(message);
}
}
@Log(secure = true)
private void setMailServerSettings(final Map<String, Object> settings) {
this.mailServerSettings = settings;
}
问题
造成的原因:
Question
What is causing:
-
secure = true
构造函数注释属性将被忽略;和 -
validate
和setMailServerSettings
方法要被调用和记录两次(一次安全地登录一次,一次不登录)?
- the
secure = true
constructor annotation attribute to be ignored; and - the
validate
andsetMailServerSettings
methods to be called and logged twice (once securely and once not)?
我怀疑这些问题是相关的.
I suspect the issues are related.
推荐答案
解决方案:
要解决重复问题,需要调整loggedClass()
切入点定义:
@Pointcut("execution(* (@Log *) .*(..)) && !@annotation(Log)")
public void loggedClass() { }
请在其他信息部分中找到指向概念验证的链接.
Please also find a link to Proof of concept in the Additional information section.
与连接点有关的问题(由@Pointcut
注释定义),它们的模式相互交叉-这就是日志中重复的原因.
Issue related to join points (defined by @Pointcut
annotation), their patterns cross each other - and this is the reason of duplication in the logs.
在我们的例子中,所有@Pointcut
都具有足够的描述性,例如:
In our case all @Pointcut
s named descriptive enough, e.g.:
-
loggedClass()
涵盖了用@Log
注释的类中的所有方法. -
loggedSecureMethod()
涵盖了所有由@Log(secure = true)
注释的方法.其他人与该人相似,因此我们忽略它们进行解释.
loggedClass()
covers all methods in the classes annotated by@Log
.loggedSecureMethod()
covers all methods annotated by@Log(secure = true)
. Others remain are similar to this one, so let's ignore them for explanation.
因此,如果EmailPayloadImpl
用@Log
注释并且EmailPayloadImpl.validate()
用@Log(secure = true)
注释-我们将有2个活动连接点:1个 secure 和1个 non -安全.这将导致添加2个日志条目.
So in case when EmailPayloadImpl
is annotated with @Log
and EmailPayloadImpl.validate()
is annotated with @Log(secure = true)
- we will have 2 active join points: one secure and one non-secure. And this will cause adding 2 log entries.
假设我们要在注释中引入优先级,即方法级别的注释应覆盖类级别的注释-最简单的方法就是避免交叉连接点模式.
Assuming that we want to introduce priority in the annotations, i.e. method-level annotation should overwrite class-level one - the simplest way will be just to avoid crossing join point patterns.
因此我们将需要3组方法:
So we will need to have 3 groups for methods:
- 用
@Log(secure = true)
=loggedSecureMethod()
注释的方法
- 用
@Log
=loggedMethod()
注释的方法
-
没有
@Log
批注但在带有@Log
批注的类中的方法,该方法是:
- Methods annotated with
@Log(secure = true)
=loggedSecureMethod()
- Methods annotated with
@Log
=loggedMethod()
Methods without
@Log
annotation, but within a class annotated with@Log
, which is:
@Pointcut("execution(* (@Log *) .*(..)) && !@annotation(Log)")
public void loggedClass() { }
其他信息:
- 如果在类级别还需要处理
@Log(secure = true)
,则需要添加与loggedClass()
类似的附加连接点. - 在 GitHub 中添加了概念验证>> . >
- In case it will be needed to handle also
@Log(secure = true)
on the class level - need to add additional join point similar tologgedClass()
of course. - Added Proof of concept >> in the GitHub
这篇关于各种切入点表达式作用域意外触发多个建议调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!