2016-02-24 9 views
2

Я использую AspectJ в Java для регистрации вызовов на некоторые методы. Я смотрел онлайн, но не смог найти ответ на этот вопрос:AspectJ Обработка нескольких советов по сопоставлению

Что происходит, когда два совета @Around соответствуют методу?

В частности, я использую два @Around советов, как это:

@Around("condition1() && condition2() && condition3()") 
public Object around(ProceedingJoinPoint point) { 
    return around(point, null); 
} 

@Around("condition1() && condition2() && condition3() && args(request)") 
public Object around(ProceedingJoinPoint point, Object request) { 
    ... 
    result = (Result) point.proceed(); 
    ... 
} 

Будет ли этот результат в point.proceed() вызывается дважды (с фактическим методом, называемым в два раз), если оба этого матч советов?

+0

В общем случае, если AspectJ не может логически рассуждать о том, что одно условие включает другое, оно не может устранить необходимость в обоих. Если бы он * мог делать такие рассуждения, вы могли бы утверждать оба способа, что он должен делать. (Я не знаю). Я предполагаю, что, поскольку у вас есть эти два совета, вы действительно знаете ответ в силу его запуска. Почему бы вам не рассказать нам? –

ответ

5

Ваш подход очень проблематичен, потому что вы вручную называете один совет другим. Это не так, как следует применять АОП. Пожалуйста, пусть AspectJ решит, какие советы выполнять по их соответствующим точкам. То, как вы делегируете от одного совета другому, вы могли бы даже назвать совет, который не соответствовал бы сам по себе. Пример в простом AspectJ без весны (работает так же в Spring AOP, хотя):

Java приложение драйвера:

package de.scrum_master.app; 

public class Application { 
    private static void doSomething() { 
     System.out.println("Doing something"); 
    } 

    public static void main(String[] args) { 
     doSomething(); 
    } 
} 

Формат:

package de.scrum_master.aspect; 

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 

@Aspect 
public class MyBogusAspect { 
    @Around("execution(* doSomething(..))") 
    public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     System.out.println("matching advice called on joinpoint " + thisJoinPoint); 
     return nonMatchingAdvice(thisJoinPoint); 
    } 

    @Around("execution(* doSomethingElse(..))") 
    public Object nonMatchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     System.out.println("non-matching advice called on joinpoint " + thisJoinPoint); 
     return thisJoinPoint.proceed(); 
    } 
} 

журнала консоли:

matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething()) 
non-matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething()) 
Doing something 

Вы видите, как нездоровится ваш прием? Совет, который иначе не соответствовал бы, называется подходящим. Это дает некоторое действительно неожиданное поведение ИМО. Пожалуйста, не делайте этого !!!

Теперь, как для первоначального вопроса о множественных советах согласующих, это то, как вы должны это сделать:

Модифицированного аспект:

package de.scrum_master.aspect; 

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 

@Aspect 
public class MyBetterAspect { 
    @Around("execution(* doSomething(..))") 
    public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     System.out.println(">>> matching advice on " + thisJoinPoint); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< matching advice on " + thisJoinPoint); 
     return result; 
    } 

    @Around("execution(* doSomething(..))") 
    public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     System.out.println(">>> another matching advice on " + thisJoinPoint); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< another matching advice on " + thisJoinPoint); 
     return result; 
    } 
} 

Нового журнал консоль:

>>> matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
Doing something 
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething()) 

Как вы можете видеть, AspectJ или Spring AOP wrap mult согласующие друг с другом рекомендации, такие как луковые скины вокруг точек соединения, и только самая внутренняя proceed() вызывает фактическую точку соединения, в то время как внешние слои называют внутренние, следя за тем, чтобы каждая точка соединения выполнялась только один раз. Вам не нужно пытаться быть умнее, чем структура AOP, что может привести к повреждению (см. Мой первый пример).

Еще одна вещь: если несколько аспектов имеют соответствующие точки, вы можете влиять на их порядок выполнения с помощью @DeclarePrecedence в AspectJ, но в рамках одного аспекта вы не имеете никакого влияния на порядок выполнения или, по крайней мере, вы не должны полагаться на него. В Spring AOP вы можете использовать аннотацию @Order, чтобы определить приоритет аспект, но порядок также не определен для нескольких советов из того же аспекта, см. Также Spring manual.


Update 2016-02-28, 18:30 CET, после некоторого обсуждения в комментариях:

Хорошо, мы расширим класс драйвера немного, чтобы мы могли проверить некоторые больше:

package de.scrum_master.app; 

public class Application { 
    private static void doSomething() { 
     System.out.println("Doing something"); 
    } 

    private static String doSomethingElse(String text) { 
     System.out.println("Doing something else"); 
     return text; 
    } 

    private static int doAnotherThing(int i, int j, int k) { 
     System.out.println("Doing another thing"); 
     return (i + j) * k; 
    } 

    public static void main(String[] args) { 
     doSomething(); 
     doSomethingElse("foo"); 
     doAnotherThing(11, 22, 33); 
    } 
} 

Теперь привязка первого параметра в AspectJ так же просто, как args(request, ..), который работает для одного или нескольких параметров. Единственным исключением являются нулевые параметры, и в этом случае pointcut не срабатывает. Так как я в конечном итоге с чем-то подобным тому, что вы сделали:

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 BoundFirstParameterAspect { 
    @Pointcut("execution(* do*(..))") 
    public static void myPointcut() {} 

    @Around("myPointcut()") 
    public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     return anotherMatchingAdvice(thisJoinPoint, null); 
    } 

    @Around("myPointcut() && args(request, ..)") 
    public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint, Object request) { 
     System.out.println(">>> another matching advice on " + thisJoinPoint); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< another matching advice on " + thisJoinPoint); 
     return result; 
    } 
} 

Что делает тот же самый совет огонь дважды, и, таким образом, вызывает накладные расходы, даже если оригинальный метод вызывается только один раз, но вы можете увидеть накладные расходы в журнале:

>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
Doing something 
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
Doing something else 
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 
Doing another thing 
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 

Вы можете легко узнать, как двойные советы увольняются для каждой точки соединения.

В качестве альтернативы, вы можете связать параметр во время выполнения, что не очень элегантный и берет на себя немного времени исполнения пенальти, но работает отлично:

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 BoundFirstParameterAspect { 
    @Pointcut("execution(* do*(..))") 
    public static void myPointcut() {} 

    @Around("myPointcut()") 
    public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) { 
     System.out.println(">>> matching advice on " + thisJoinPoint); 
     Object[] args = thisJoinPoint.getArgs(); 
     Object request = args.length > 0 ? args[0] : null; 
     System.out.println("First parameter = " + request); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< matching advice on " + thisJoinPoint); 
     return result; 
    } 
} 

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

>>> matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
First parameter = null 
Doing something 
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething()) 
>>> matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
First parameter = foo 
Doing something else 
<<< matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String)) 
>>> matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 
First parameter = 11 
Doing another thing 
<<< matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int)) 

Последнее, но не менее, вы можете иметь две немного разные - один срезов в с пустой args() и один с args(request, ..) - оба из которых могут делегировать обработку параметров , Регистрация и обработка исключений на вспомогательный метод для того, чтобы избежать дублирования, как я уже говорил в одном из моих комментариев:

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 BoundFirstParameterAspect { 
    @Pointcut("execution(* do*(..))") 
    public static void myPointcut() {} 

    @Around("myPointcut() && args()") 
    public Object myAdvice(ProceedingJoinPoint thisJoinPoint) { 
     return myAdviceHelper(thisJoinPoint, null); 
    } 

    @Around("myPointcut() && args(request, ..)") 
    public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) { 
     return myAdviceHelper(thisJoinPoint, request); 
    } 

    private Object myAdviceHelper(ProceedingJoinPoint thisJoinPoint, Object request) { 
     System.out.println(">>> matching advice on " + thisJoinPoint); 
     System.out.println("First parameter = " + request); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< matching advice on " + thisJoinPoint); 
     return result; 
    } 
} 

Бревно консоль должна быть точно такой же, как и предыдущий.


Update 2:

Ну, я просто понял, что пустой args() трюк будет также применяться к вашим оригинальным идеям и избежать двойного исполнения, а также вспомогательный метода:

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 BoundFirstParameterAspect { 
    @Pointcut("execution(* do*(..))") 
    public static void myPointcut() {} 

    @Around("myPointcut() && args()") 
    public Object myAdvice(ProceedingJoinPoint thisJoinPoint) { 
     return myAdviceWithParams(thisJoinPoint, null); 
    } 

    @Around("myPointcut() && args(request, ..)") 
    public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) { 
     System.out.println(">>> matching advice on " + thisJoinPoint); 
     System.out.println("First parameter = " + request); 
     Object result = thisJoinPoint.proceed(); 
     System.out.println("<<< matching advice on " + thisJoinPoint); 
     return result; 
    } 
} 

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

+0

Ну, я чувствовал, что это запах кода, но на самом деле это именно то, что я хочу делать. Я хочу вызвать этот метод для методов с любым количеством параметров, но если указан только один параметр, я хочу, чтобы он поместил его в объект запроса. Знаете ли вы о каком-либо лучшем способе достижения этого без дублирования кода? Удаление дублирования кода на самом деле является основной причиной, по которой мы начали использовать AspectJ. –

+0

Да, я знаю лучший способ. Но еще несколько вопросов: если есть нулевые параметры, следует ли также пожаловаться? Если имеется более одного параметра, должен ли первый быть привязан? Должен ли первый параметр всегда привязываться или только если он соответствует определенному типу? А как насчет вашего страха дублирования? Сколько кода в фактических советах? Всего несколько строк или многое другое? Почему бы не включить его в обычный метод? Еще один раз: ** Никогда ** не назовите один совет от другого, я доказал вам с моим первым примером, насколько это нездорово. – kriegaex

+0

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

0

Please see kriegaex's answer for more details. Оставляя это здесь для полноты.

Я закончил реализацию фиктивного проекта, чтобы проверить это. Ответ заключается в том, что метод будет выполнен только один раз. Я реализовал следующее:

Aspects.java:

package base; 

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 Aspects { 
    @Pointcut("@annotation(java.lang.Deprecated)") 
    public void hasErrorResponseMethod() { 
    } 

    @Around("hasErrorResponseMethod()") 
    public Object around(ProceedingJoinPoint point) throws Throwable { 
     System.out.println("In CHILD advice."); 
     return around(point, null); 
    } 

    @Around("hasErrorResponseMethod() && args(request)") 
    public Object around(ProceedingJoinPoint point, Object request) throws Throwable { 
     System.out.println("In PARENT advice with request " + request); 
     return point.proceed(); 
    } 
} 

Methods.java:

package base; 

public class Methods { 
    private static int COUNT = 1; 
    @Deprecated 
    public int method1(String param) { 
     System.out.println(">>> In method1! Param: " + param); 
     return COUNT++; 
    } 
} 

applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:context="http://www.springframework.org/schema/context" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:aop="http://www.springframework.org/schema/aop" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.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-3.0.xsd"> 

    <context:component-scan base-package="base" annotation-config="true" /> 

    <aop:aspectj-autoproxy /> 

    <bean id="logAspect" class="base.Aspects"/> 

    <bean id="methods" class="base.Methods"/> 
    <bean id="main" class="base.Main"/> 

</beans> 

Main.java:

package base; 

import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 

public class Main { 
    public static void main(String[] args) { 
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 

     Methods methods = (Methods) context.getBean("methods"); 
     System.out.println("<<< Result: " + Methods.method1("REQUEST_VALUE")); 
    } 
} 

Выход следующий:

In PARENT advice with request REQUEST_VALUE 
In CHILD advice. 
In PARENT advice with request null 
>>> In method1! Param: REQUEST_VALUE 
<<< Result: 1 

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

+0

Я предполагаю, что руководство AspectJ говорит вам где-то. –

+0

Пожалуйста, ** никогда не используйте аспекты, подобные этому, одним советом, вызывающим другой! См. Мой собственный ответ, доказывая возможные нежелательные побочные эффекты и объясняя, что делать вместо этого. – kriegaex