Spring AOP и AspectJ Load-Time Weaving: рекомендации Around будут вызываться дважды для частных методов.

Я буду использовать Spring AOP и AspectJ Load-Time Weaving для измерения времени выполнения определенных частных/защищенных/общедоступных методов в моем коде.

Для этого я написал следующую аннотацию, в которой я буду аннотировать методы, время выполнения которых должно быть измерено:

package at.scan.spring.aop.measuring;

import org.aspectj.lang.ProceedingJoinPoint;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation for pointcut associated with the advice {@link MeasuringAspect#aroundAdvice(ProceedingJoinPoint)}.
 * @author ilyesve
 * @since 02.12.2015
 */
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Measured {

}

Я также написал следующий Аспект:

package at.scan.spring.aop.measuring;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An aspect which contains an advice to measure execution of methods that are annotated with {@link Measured} if it
 * is enabled.
 * After the execution of the annotated method the captured data over its execution will be forwarded to the
 * configured {@link MeasuringReporter}.
 * @author ilyesve
 * @since 02.12.2015
 */
@Aspect
public class MeasuringAspect {

    /** LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(MeasuringAspect.class.getPackage().getName());

    /** Determines whether the Around advice is enabled. Default is disabled. */
    private boolean enabled = false;

    /** The {@link MeasuringReporter} to report the captured measuring data. */
    private MeasuringReporter reporter;

    /**
     * The Around advice which will be executed on calling of methods annotated with {@link Measured}.
     * @param pjp the join point
     * @throws Throwable on failure
     * @return result of proceeding of the join point
     */
    @Around("@annotation(Measured)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;

        if (enabled && reporter != null) {
            LOGGER.debug("Starting measuring of method '{}.{}()'...",
                    pjp.getSignature().getDeclaringTypeName(),
                    pjp.getSignature().getName());

            MeasuringDataDto measuringData = new MeasuringDataDto(pjp.getSignature(), pjp.getArgs());

            measuringData.setStartTs(System.currentTimeMillis());
            try {
                measuringData.setResult(pjp.proceed());
            } catch (Throwable t) {
                measuringData.setThrowable(t);
            }
            measuringData.setEndTs(System.currentTimeMillis());

            try {
                reporter.report(measuringData);
            } catch (Throwable t) {
                LOGGER.error("Unable to report captured measuring data because of an error. MeasuringData [{}]",
                        ReflectionToStringBuilder.toString(measuringData, ToStringStyle.DEFAULT_STYLE, true, true),
                        t);
            }

            if (measuringData.getThrowable() != null) {
                throw measuringData.getThrowable();
            }

            result = measuringData.getResult();
        } else {
            result = pjp.proceed();
        }

        return result;
    }

    /**
     * @param theEnabled if {@code true} the contained advice will be enabled, otherwise disabled
     */
    public final void setEnabled(final boolean theEnabled) {
        enabled = theEnabled;
        if (enabled && reporter != null) {
            LOGGER.info("Methods will be measured. Reporter [{}]", reporter.getClass().getCanonicalName());
        }
    }

    /**
     * @param theReporter the {@link MeasuringReporter} to be used to report the captured measuring data about
     *                    execution of an method annotated with {@link Measured}
     */
    public final void setReporter(final MeasuringReporter theReporter) {
        reporter = theReporter;
        if (enabled && reporter != null) {
            LOGGER.info("Methods will be measured. Reporter [{}]", reporter.getClass().getCanonicalName());
        }
    }
}

Моя конфигурация Spring выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"
       default-autowire="byName">

    <context:load-time-weaver aspectj-weaving="autodetect" />

    <bean id="measuringAspect" class="at.scan.spring.aop.measuring.MeasuringAspect"
          factory-method="aspectOf">
        <property name="enabled" value="${measuring.enabled}" />
        <property name="reporter" ref="measuringReporterService" />
    </bean>
</beans>

Я также поместил в каталог src/main/resources/META-INF моего проекта следующие aop.xml:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <include within="at.scan..*" />
    </weaver>
    <aspects>
        <aspect name="at.scan.spring.aop.measuring.MeasuringAspect" />
    </aspects>
</aspectj>

Также я добавил в свой POM следующие зависимости Spring AOP и/или AspectJ:

  • орг.аспектж:аспектжрт:1.8.6
  • орг.аспектж:аспектжтулс:1.8.6
  • org.springframework:весна-аоп:4.1.6

Кроме того, я использую org.aspectj:aspectweaver:1.8.6 в качестве агента Java, запуская свой Tomcat.

Все работает нормально для аннотированных общедоступных и защищенных методов, но для аннотированных частных методов рекомендации моего аспекта будут вызываться дважды, и я не знаю, почему.


person Zsepii    schedule 11.12.2015    source источник
comment
Есть реакция на мой ответ? Это решает вашу проблему?   -  person Nándor Előd Fekete    schedule 06.01.2016


Ответы (2)


Ваше выражение pointcut соответствует всем точкам соединения, где тема точки соединения имеет аннотацию @Measured. Сюда входят точки соединения типа как выполнение метода, так и вызов метода. Вероятно, вы видите, что совет дважды выполняется для частных методов только потому, что ваши частные методы вызываются локально из рекомендуемых классов. Если у вас есть вызовы методов из рекомендуемого кода в @Measured аннотированных методов другой видимости, вы увидите выполнение двойного совета и для этих методов, а не только для частных методов. Решение состоит в том, чтобы изменить выражение pointcut, чтобы ограничить точку соединения method-execution или method-call. Я предполагаю, что в вашем случае будет само выполнение метода, поэтому ваше выражение pointcut станет следующим:

@Around("execution(@Measured * *(..))")

Поскольку вы нигде не привязываете аннотацию в своем совете, вам даже не нужна часть @annotation(Measured).

При настройке новых аспектов в вашем проекте всегда полезно проверить процесс создания, включив -showWeaveInfo и -verbose в файле aop.xml.

<weaver options="-showWeaveInfo -verbose">
...
</weaver>

Это приведет к отображению сообщений журнала о стандартной ошибке, подобных этим (обратите внимание также на номера строк):

[AppClassLoader@62b103dd] weaveinfo Join point 'method-call(void at.scan.spring.aop.measuring.MeasuredClass.test3())' in Type 'at.scan.spring.aop.measuring.MeasuredClass' (MeasuredClass.java:18) advised by around advice from 'at.scan.spring.aop.measuring.MeasuringAspect' (MeasuringAspect.java)
[AppClassLoader@62b103dd] weaveinfo Join point 'method-execution(void at.scan.spring.aop.measuring.MeasuredClass.test3())' in Type 'at.scan.spring.aop.measuring.MeasuredClass' (MeasuredClass.java:27) advised by around advice from 'at.scan.spring.aop.measuring.MeasuringAspect' (MeasuringAspect.java)
person Nándor Előd Fekete    schedule 23.12.2015

@Around("execution(* *(..)) && @annotation(Measured)")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
   ...
}

Добавление исполнения(* *(..))

person chen_767    schedule 16.12.2016