各种切入点表达式范围会意外触发多个建议调用



Background

使用以下方面记录项目:使用@Log批注标记的所有方法、类和构造函数都将信息写入日志文件。

问题

方法似乎以递归方式称为一级深度,但代码没有显示任何此类递归关系。

实际

记录的结果:

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}])

预期

预期记录的结果:

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])

法典

日志记录方面:

@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:

@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;
}

问题

导致什么:

  • 要忽略的构造函数注释属性secure = true;以及
  • validatesetMailServerSettings方法调用和记录两次(一次安全,一次不安全(?

我怀疑这些问题是相关的。

解决方案:

要解决重复问题,需要调整loggedClass()切入点定义:

@Pointcut("execution(* (@Log *) .*(..)) && !@annotation(Log)")
public void loggedClass() { }

另请在"其他信息"部分找到指向概念验证的链接。


解释:

与连接点(由@Pointcut注释定义(相关的问题,它们的模式相互交叉 - 这就是日志中重复的原因。

在我们的例子中,所有@Pointcut都有足够的描述性,例如:

  • loggedClass()涵盖了由@Log注释的类中的所有方法。
  • loggedSecureMethod()涵盖了由@Log(secure = true)注释的所有方法。其他剩余的与此相似,因此让我们忽略它们以进行解释。

因此,如果EmailPayloadImpl@Log注释而EmailPayloadImpl.validate()@Log(secure = true)注释 - 我们将有 2 个活动连接点:一个安全连接点和一个不安全连接点。这将导致添加 2 个日志条目。


假设我们想在注解中引入优先级,即方法级注解应该覆盖类级- 最简单的方法是避免交叉连接点模式。

因此,我们需要有 3 组方法:

  1. @Log(secure = true)=loggedSecureMethod()注释的方法
  2. @Log=loggedMethod()注释的方法
  3. 没有@Log注释的方法,但在用@Log注释的类中,即:

    @Pointcut("execution(* (@Log *) .*(..)) && !@annotation(Log)")
    public void loggedClass() { }
    

附加信息:

  1. 如果需要在类级别处理@Log(secure = true)- 当然需要添加类似于loggedClass()的额外连接点。
  2. 在 GitHub中添加了概念验证>>

基本上,您的示例有两个问题,导致重复和"忽略"安全注释。

首先,你有EmailPayloadImpl@Log类级注释和一个名为loggedClass的切入@Pointcut("execution(* (@Log *) .*(..))"),它基本上对所有类方法(包括构造函数(应用log。因此,这意味着对EmailPayloadImpl的任何方法调用最终都会以不安全的日志结束,因为secure属性的默认值falseLog注释中。

第二个问题是构造函数的切入点不正确,这就是忽略secure = true的原因。您正在使用可见性关键字,这在为构造函数创建切入点时不正确。所以切入点

@Pointcut("execution(public @Log( secure = true ) *.new(..))")
public void loggedSecureConstructor() { }

应改为

@Pointcut("execution(@Log( secure = true ) *.new(..))")
public void loggedSecureConstructor() { }

唯一的区别是删除了public可见性关键字。loggedConstructor()也应该这样做。

因此,当您构造函数切入点不正确时,@Log注释 构造函数被忽略,因为没有应该有的切入点 用过它。类上的@Log只是为所有应用日志 包括构造函数的类方法。

制作您的示例,因为您需要修复切入点并删除类级别注释。

看到你是你的示例的修复

让我们修复切入点LogAspect

@Aspect
public class LogAspect {
@Pointcut("execution(@Log( secure = true ) *.new(..))")
public void loggedSecureConstructor() { }
@Pointcut("execution(@Log( secure = true ) * *.*(..))")
public void loggedSecureMethod() { }
@Pointcut("execution(@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批注

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;
}

相关内容

  • 没有找到相关文章

最新更新