Изменение параметров в нескольких аспектах, предоставляющих советы

У меня есть два аспекта, каждый из которых модифицирует аргументы метода. Когда оба аспекта применяются к одному и тому же методу, я ожидаю, что выполнение аспектов будет привязано, и я ожидаю, что аргументы, модифицированные в первом аспекте, будут доступны во втором аспекте через joinPoint.getArgs(); Однако, похоже, что каждый аспект получает только исходные аргументы; второй аспект никогда не видит измененных значений. Я придумал пример:

Класс тестирования:

public class AspectTest extends TestCase {
    @Moo
    private void foo(String boo, String foo) {
        System.out.println(boo + foo);
    }

    public void testAspect() {
        foo("You should", " never see this");
    }
}

Метод foo() рекомендуется двумя аспектами:

@Aspect
public class MooImpl {

    @Pointcut("execution(@Moo * *(..))")
    public void methodPointcut() {}

    @Around("methodPointcut()")
    public Object afterMethodInControllerClass(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("MooImpl is being called");
        Object[] args = joinPoint.getArgs();
        args[0] = "don't";
        return joinPoint.proceed(args);
    }
}

и...

@Aspect
public class DoubleMooImpl {

    @Pointcut("execution(@Moo * *(..))")
    public void methodPointcut() {}

    @Around("methodPointcut()")
    public Object afterMethodInControllerClass(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("DoubleMooImpl is being called");
        Object[] args = joinPoint.getArgs();
        args[1] = " run and hide";
        return joinPoint.proceed(args);
    }
}

Я ожидаю, что выход будет следующим:

MooImpl is being called
DoubleMooImpl is being called
don't run and hide

... но есть:

MooImpl is being called
DoubleMooImpl is being called
You should run and hide

Я использую правильный подход для изменения аргументов через советы?

Ответ 1

После того, как я был на дороге в течение нескольких дней, я, наконец, обдумал это и ответил на ваш вопрос. Я надеюсь, что это нормально, если я использую собственный синтаксис AspectJ, потому что я не чувствую себя комфортно с нотами @AspectJ.

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Moo {}
public class AspectTest {
    @Moo
    private void foo(String boo, String foo) {
        System.out.println(boo + foo);
    }

    public static void main(String[] args) {
        new AspectTest().foo("You should", " never see this");
    }
}
public aspect MooImpl {
    pointcut methodPointcut() : execution(@Moo * *(String, ..));

    Object around(String firstArg) : methodPointcut() && args(firstArg, ..) {
        System.out.println("MooImpl is being called");
        return proceed("don't");
    }
}
public aspect DoubleMooImpl {
    pointcut methodPointcut() : execution(@Moo * *(*, String, ..));

    Object around(String secondArg) : methodPointcut() && args(*, secondArg, ..) {
        System.out.println("DoubleMooImpl is being called");
        return proceed(" run and hide");
    }
}

Ваша ошибка состояла в том, чтобы предположить, что вы можете манипулировать аргументами, полученными с помощью getArgs(), что неверно. Чтобы передать аргументы proceed(), вам нужно обратиться к ним через args(), что я продемонстрировал выше. Обратите внимание на синтаксис для извлечения первого и второго аргументов String в двух аспектах.

Это должно решить вашу проблему.

Ответ 2

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

Вместо этого вы можете попробовать передать в StringBuilder или какой-либо другой изменяемый тип, а затем изменить состояние, то изменение состояния должно быть правильно отображено.

Update:

Я тестировал с изменяемым типом и менялся, как ожидалось:

@Moo
private void foo(StringBuilder boo, StringBuilder foo) {
    System.out.println(boo.toString() + foo.toString());
}

public void testAspect() {
    foo(new StringBuilder("You should"), new StringBuilder(" never see this"));
}

С аспектом MooImpl:

@Aspect
public class MooImpl {

    @Pointcut("execution(@Moo * *(..))")
    public void methodPointcut() {}

    @Around("methodPointcut()")
    public Object afterMethodInControllerClass(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("MooImpl is being called");
        Object[] args = joinPoint.getArgs();
        ((StringBuilder)args[0]).append("****");
        return joinPoint.proceed(args);
    }
}

и DoubleMooImpl:

@Aspect
public class DoubleMooImpl {

    @Pointcut("execution(@Moo * *(..))")
    public void methodPointcut() {}

    @Around("methodPointcut()")
    public Object afterMethodInControllerClass(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("DoubleMooImpl is being called");
        Object[] args = joinPoint.getArgs();
        ((StringBuilder)args[1]).append("****");
        return joinPoint.proceed(args);
    }
}

и получить этот вывод:

MooImpl is being called
DoubleMooImpl is being called
You should**** never see this****