Various pointcut expression scopes trigger multiple advice calls unexpectedly

匆匆过客 提交于 2020-01-14 14:28:13

问题


Background

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.

Problem

Methods appear to be called recursively one-level deep, but the code does not show any such recursive relationship.

Actual

Logged results:

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

The Log interface definition:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface Log {
    boolean secure() default false;
}

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

The payload implementation:

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

  • the secure = true constructor annotation attribute to be ignored; and
  • the validate and setMailServerSettings methods to be called and logged twice (once securely and once not)?

I suspect the issues are related.


回答1:


Solution:

To fix duplication issue need to adjust loggedClass() pointcut definition:

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

Please also find a link to Proof of concept in the Additional information section.


Explanation:

Issue related to join points (defined by @Pointcut annotation), their patterns cross each other - and this is the reason of duplication in the logs.

In our case all @Pointcuts named descriptive enough, e.g.:

  • 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.

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.

So we will need to have 3 groups for methods:

  1. Methods annotated with @Log(secure = true) = loggedSecureMethod()
  2. Methods annotated with @Log = loggedMethod()
  3. Methods without @Log annotation, but within a class annotated with @Log, which is:

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

Additional information:

  1. In case it will be needed to handle also @Log(secure = true) on the class level - need to add additional join point similar to loggedClass() of course.
  2. Added Proof of concept >> in the GitHub



回答2:


Basically your example has two issues which are causing the the duplication and the "ignoring" of the secured annotation.

First of all you have a @Log class level annotation in EmailPayloadImpl and a pointcut @Pointcut("execution(* (@Log *) .*(..))") named loggedClass which basically applies log on all of the class methods including the constructors. So that means that any method call on EmailPayloadImpl will end up with insecure log as the default value for secure attribute is false in Log annotation.

Second issue is the fact that your pointcuts for constructors are not correct that is why the secure = true is ignored. You are using the visibility keywords which is not correct when creating pointcuts for constructors. So the pointcut

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

Should be changed to

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

The only difference is removal of the public visibility keyword. Same should be done for loggedConstructor().

So as you constructor pointcuts were incorrect the @Log annotation on the constructor was ignored as there was no pointcut which should have used it. And the @Log on the class was just applying log for all of the class methods including the constructor.

Tho make your example for you need to fix the pointcuts and remove the class level annotation.

See your a fix of your example

Lets fix the pointcuts in 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);
    }
}

Remove the class level @Log annotation

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


来源:https://stackoverflow.com/questions/52506915/various-pointcut-expression-scopes-trigger-multiple-advice-calls-unexpectedly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!