Вы можете использовать шаблон execution(...) && !cflowbelow(execution(...))
.Это не хорошо для производительности, потому что путь выполнения (например, callstack) должен проверяться во время выполнения, а не во время компиляции, но он делает то, что вы хотите.Остерегайтесь некоторых ключевых отличий из-за непрокси-природы AspectJ и из-за более широкого набора точек соединения и точек доступа, доступных по сравнению с другими средами AOP, такими как перехват частных или статических методов.
Теперь вотНебольшой пример в соответствии с тем, что вы описали:
package de.scrum_master.core.annotation;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface SkipHydrationInterception {}
package de.scrum_master.service.foo.bar.impl;
import de.scrum_master.core.annotation.SkipHydrationInterception;
public class MyServiceImpl {
public void method1() {
// We do not want the below internal call to be intercepted.
method2();
}
public void method2() {
// If some other class's method calls this, intercept the call. But do not
// intercept the call from method1().
}
@SkipHydrationInterception
public void method3() {
// Always skip this method one due to the annotation.
// Should this one be intercepted or not?
// method1();
}
public static void main(String[] args) {
MyServiceImpl service = new MyServiceImpl();
service.method1();
System.out.println("-----");
service.method2();
System.out.println("-----");
service.method3();
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class ServiceAspectJHydrationInterceptor {
@Pointcut("execution(public !static * de.scrum_master.service..impl.*ServiceImpl.*(..))")
public void serviceLayerPublicMethods() {}
@Pointcut("@annotation(de.scrum_master.core.annotation.SkipHydrationInterception)")
public void skipHydrationInterception() {}
@Pointcut("serviceLayerPublicMethods() && !skipHydrationInterception()")
public void interceptMe() {}
@Around("interceptMe() && !cflowbelow(interceptMe())")
public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(pjp);
return pjp.proceed();
}
}
Теперь запустите приложение драйвера, и вы увидите этот журнал консоли:
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
-----
Это точното, что ты хочешь.Все идет нормально.Также обратите внимание на квалификатор !static
в точке выполнения, поскольку в противном случае static main(..)
будет перехвачен.
Но теперь раскомментируйте вызов method1()
внутри тела method3()
.Журнал консоли становится:
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
Вопрос: это то, что вы хотите?method1()
вызывается методом, который был исключен из перехвата из-за его аннотации, но с другой стороны, это также внутренний вызов метода, мне нравится называть его самовывозом.Решение зависит от вашего ответа.
Также обратите внимание, что открытые методы, вызываемые из закрытых или защищенных методов того же класса, также будут перехватываться.Так что cflow()
или cflowbelow()
не заботятся о самопризыве, только о указанном потоке управления.
Другая ситуация: если перехваченный публичный метод по какой-то причине вызовет другой класс, и этот класс снова вызоветоткрытый метод первого класса, !cflowbelow(...)
по-прежнему исключает перехват этого вызова, поскольку первый вызов уже находится в потоке управления.
Следующая ситуация: один открытый метод *ServiceImpl
вызывает другой открытый *ServiceImpl
метод.Результатом также будет то, что второй вызываемый метод не будет перехвачен, потому что что-то, соответствующее его точке выполнения, уже находится в потоке управления (стек вызовов).
Так что мое решение, даже если мы настроим эти точки, чтобы покрытьВ нескольких ключевых случаях это не то же самое, что решение на основе прокси по своей природе.Если в вашей среде могут произойти такие угловые случаи, как описанные, вам действительно следует провести рефакторинг аспектов, чтобы вести бухгалтерский учет (сохранение состояния) и / или использовать другую модель создания экземпляров, такую как percflowbelow
(но не думал, чтоодин, потому что я не знаю ваших точных требований).Но ТАК не дискуссионный форум, и я не могу помочь вам здесь постепенно.Не стесняйтесь проверить контактные данные (например, Telegram) в моем профиле SO и нанять меня, если вам нужна более глубокая поддержка.Но, может быть, вы также можете взять его отсюда, я просто упоминаю об этом.
Обновление:
Хорошо, я придумал способ эмулировать проксиповедение на основе AOP через AspectJ.Мне это не нравится, и оно требует, чтобы вы переключились с execution()
на call()
pointcut, т.е. вам больше не нужно контролировать (аспектное переплетение) вызываемый объект (исполняемый код), а вызывать вызывающий объект (источник вызова методаперехвачено).
Вам также понадобится проверка во время выполнения между двумя объектами this()
и target()
из if()
pointcut.Мне это тоже не нравится, потому что это замедляет ваш код и требует проверки во многих местах.Если вы все еще можете достичь своей цели повышения производительности по сравнению с прокси-решением, от которого вы хотите избавиться, вы должны проверить это самостоятельно.Помните, вы теперь эмулируете то, что хотите отменить, LOL.
Давайте добавим еще один класс, чтобы имитировать взаимодействие внешнего класса, вызывающего целевой класс, в дополнение к простому вызову его из статического метода, которыйнедостаточно для проверки.
package de.scrum_master.service.foo.bar.impl;
public class AnotherClass {
public void doSomething() {
MyServiceImpl service = new MyServiceImpl();
service.method1();
System.out.println("-----");
service.method2();
System.out.println("-----");
service.method3();
System.out.println("-----");
}
}
Исходный класс MyServiceImpl
мы немного расширили, регистрируя больше и также вызывая AnotherClass.doSomething()
.
package de.scrum_master.service.foo.bar.impl;
import de.scrum_master.core.annotation.SkipHydrationInterception;
public class MyServiceImpl {
public void method1() {
System.out.println("method1");
method2();
}
public void method2() {
System.out.println("method2");
}
@SkipHydrationInterception
public void method3() {
System.out.println("method3");
method1();
}
public static void main(String[] args) {
MyServiceImpl service = new MyServiceImpl();
service.method1();
System.out.println("-----");
service.method2();
System.out.println("-----");
service.method3();
System.out.println("-----");
new AnotherClass().doSomething();
}
}
Улучшенный аспект выглядит так:это:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class ServiceAspectJHydrationInterceptor {
@Pointcut("call(public !static * de.scrum_master.service..impl.*ServiceImpl.*(..))")
public void serviceLayerPublicMethods() {}
@Pointcut("@annotation(de.scrum_master.core.annotation.SkipHydrationInterception)")
public void skipHydrationInterception() {}
@Pointcut("serviceLayerPublicMethods() && !skipHydrationInterception()")
public void interceptMe() {}
@Pointcut("if()")
public static boolean noSelfInvocation(ProceedingJoinPoint thisJoinPoint) {
return thisJoinPoint.getThis() != thisJoinPoint.getTarget();
}
@Around("interceptMe() && noSelfInvocation(thisJoinPoint)")
public Object invoke(ProceedingJoinPoint thisJoinPoint, JoinPoint.EnclosingStaticPart thisEnclosingStaticPart) throws Throwable {
System.out.println(thisJoinPoint);
System.out.println(" called by: " + thisEnclosingStaticPart);
return thisJoinPoint.proceed();
}
}
А теперь журнал консоли выглядит так:
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
called by: execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.main(String[]))
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
called by: execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.main(String[]))
method2
-----
method3
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
called by: execution(void de.scrum_master.service.foo.bar.impl.AnotherClass.doSomething())
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
called by: execution(void de.scrum_master.service.foo.bar.impl.AnotherClass.doSomething())
method2
-----
method3
method1
method2
-----
По моему мнению, именно так будет вести себя Spring AOP или JBoss AOP из-за своей природы прокси. Может быть, я что-то забыл, но я думаю, что я довольно хорошо раскрыл угловые случаи.
Пожалуйста, дайте мне знать, если у вас есть проблемы с пониманием этого решения. Что касается значения обозначений точек, которые я использую, пожалуйста, обратитесь к руководству AspectJ.