Различные области выражений pointcut неожиданно вызывают несколько вызовов совета - PullRequest
0 голосов
/ 26 сентября 2018

Фон

Ведение журнала проекта с использованием таких аспектов, что все методы, классы и конструкторы, помеченные аннотацией @Log, содержат информацию, записанную в файл журнала.

Проблема

Методы, кажется, называются рекурсивно глубокими на один уровень, но код не показывает никаких таких рекурсивных отношений.

Actual

Зарегистрированные результаты:

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

Декомпилированный компонент службы:

@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, который следует игнорировать;и
  • методы validate и setMailServerSettings, которые будут вызываться и регистрироваться дважды (один раз безопасно и один раз нет)?

Я подозреваю, что проблемы связаны.

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Решение:

Чтобы устранить проблему с дублированием, необходимо настроить loggedClass() Определение точки:

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

Также можно найти ссылку на Подтверждение концепции в Дополнительная информация section.


Объяснение:

Проблема, связанная с точками соединения (определяется аннотацией @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
0 голосов
/ 29 сентября 2018

В основном в вашем примере есть две проблемы, которые вызывают дублирование и «игнорирование» защищенной аннотации.

Прежде всего, у вас есть @Log аннотация уровня класса в EmailPayloadImpl и pointcut@Pointcut("execution(* (@Log *) .*(..))") с именем loggedClass, который в основном применяет log ко всем методам класса, включая конструкторы.Таким образом, это означает, что любой вызов метода в EmailPayloadImpl будет заканчиваться небезопасным журналом, поскольку значение по умолчанию для атрибута secure равно false в аннотации Log.

Второй проблемой является тот факт, что ваши указатели для конструкторов не верны, поэтому secure = true игнорируется.Вы используете ключевые слова видимости, что не правильно при создании точек для конструкторов.Поэтому pointcut

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

следует изменить на

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

Единственное отличие - удаление ключевого слова public visibility.То же самое должно быть сделано для loggedConstructor().

Таким образом, поскольку ваши указатели точки конструктора были неверны, аннотация @Log на конструкторе была проигнорирована, поскольку не было ни одного pointcut, который должен был бы его использовать.И @Log в классе просто применял log для всех методов класса, включая конструктор.

Вот ваш пример, вам нужно исправить точки и удалить аннотацию на уровне класса.

См. Исправление вашего примера

Позволяет фиксировать pointcut в 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;
}
...