Spring AOP and AspectJ Load-Time Weaving: Around advice will be invoked twice for private methods

此生再无相见时 提交于 2020-03-05 07:53:46

问题


I will use Spring AOP and AspectJ Load-Time Weaving to measure execution time of specific private/protected/public methods in my code.

To do this I wrote following annotation with one I will annotate the methods which execution time should be measured:

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 {

}

I also wrote the following Aspect:

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

My Spring configuration is as follows:

<?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>

I have also placed in the directory src/main/resources/META-INF of my project the following 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>

Also I added following Spring AOP and/or AspectJ specific dependencies to my POM:

  • org.aspectj:aspectjrt:1.8.6
  • org.aspectj:aspectjtools:1.8.6
  • org.springframework:spring-aop:4.1.6

Furthermore I use org.aspectj:aspectweaver:1.8.6 as Java Agent by starting of my Tomcat.

Everything works fine for annotated public and protected methods but for annotated private methods the around advice of my aspect will by invoked twice and I don't know why.


回答1:


Your pointcut expression matches all joinpoints where the subject of the joinpoint has the @Measured annotation. This includes both method-execution and method-call type joinpoints. You probably see the advice executing twice on private methods only because your private methods are the ones which are invoked locally from advised classes. Should you have method calls from advised code to @Measured annotated methods of other visibility, you'd see the double advice execution on those methods as well, not only private methods. The solution is to change your pointcut expression to restrict the joinpoint to either method-execution or method-call. My guess in your case would be the method execution itself, so your pointcut expression would become this:

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

Since you don't bind the annotation anywhere in your advice, you don't even need the @annotation(Measured) part.

While setting up new aspects in your project, it's always a good idea to check the weaving process by enabling -showWeaveInfo and -verbose in your aop.xml.

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

That would expose log messages on standard error similar to these (note the line numbers too):

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



回答2:


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

Adding execution(* *(..))



来源:https://stackoverflow.com/questions/34218012/spring-aop-and-aspectj-load-time-weaving-around-advice-will-be-invoked-twice-fo

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