Получение объекта Template/Generic java.lang.reflect.Method из org.aspectj.lang.ProceedingJoinPoint

Этого вопроса не было бы, если бы AspectJ работал так же, как работают перехватчики EJB.

Рассмотрим базовый сценарий EJB-перехватчика:

@AroundInvoke
public Object log(final InvocationContext ctx) throws Exception {
    // do stuff before
    final Object result = ctx.proceed();
    // do stuff after
    return result;
}

Теперь мне нужен перехватываемый объект Method. Здесь я могу просто сделать это:

        Method method = ctx.getMethod();

И все, после этого я буду проверять аннотации перехваченного метода.

Теперь я работаю с приложением, которое развернуто в контейнере сервлетов (Tomcat) и, следовательно, не имеет EJB. АОП в нем реализован с помощью Spring + AspectJ.

Метод вокруг выглядит так:

@Around("execution(* foo.bar.domain.integration.database.dao..*(..))")
public Object log(final ProceedingJoinPoint pjp) throws Throwable {
    // do stuff before
    final Object result = pjp.proceed();
    // do stuff after
    return result;
}

Здесь я больше не могу этого делать:

Method method = pjp.getMethod(); // no such method exists

Вместо этого я вынужден сам получить объект Method, используя отражение следующим образом:

    final String methodName = pjp.getSignature().getName();
    final Object[] arguments = pjp.getArgs();
    final Class[] argumentTypes = getArgumentTypes(arguments);
    final Method method = pjp.getTarget().getClass().getMethod(methodName, argumentTypes);

Он работает до тех пор, пока вы не захотите использовать метод шаблона:

@Transactional
public <T extends Identifiable> T save(T t) {
    if (null == t.getId()) {
        create(t);
        return t;
    }
    return update(t);
}

Предположим, вы вызываете этот метод следующим образом:

Person person = new Person("Oleg");
personService.save(person);

Вы получите:

Caused by: java.lang.NoSuchMethodException: foo.bar.domain.integration.database.dao.EntityService.save(foo.bar.domain.entity.Person)

который выбрасывается:

pjp.getTarget().getClass().getMethod()

Проблема в том, что дженерики не существуют во время выполнения, а фактическая сигнатура метода:

public Identifiable save(Identifiable t) {}

final Class[] argumentsTypes будет содержать один элемент и его тип будет Person. Итак, этот вызов:

pjp.getTarget().getClass().getMethod(methodName, argumentTypes);

будет искать метод:

save(Person p)

и не найдет.

Итак, вопрос в следующем: как мне получить экземпляр объекта Java template Method, который представляет точный метод, который был перехвачен?

Я предполагаю, что один из способов - сделать это методом грубой силы: получить суперклассы/интерфейсы аргументов и попытаться получить метод, используя эти типы, пока вы больше не получите NoSuchMethodException, но это просто уродливо. Есть ли нормальный чистый способ сделать это?


person Oleg    schedule 06.03.2011    source источник


Ответы (2)


MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();

Сразу не видно, в чем виноват API.

Также см. Spring AOP: как получить аннотации рекомендуемого метода. Я не проверял это на себе. ОП там говорит, что решил проблему для него. Для другого использования требовалась дополнительная аннотация @Around(annotation...). (Попробуйте, например, установить цель только METHOD и посмотреть, как она себя ведет)

person Bozho    schedule 06.03.2011
comment
Хорошо, во-первых, большое спасибо за ответ. Во-вторых, я чувствую себя плохо из-за того, что не ожидал результата getSignature() достаточно хорошо, чтобы заметить, что я могу получить оттуда объект Method. Он действительно возвращает его, спасибо за подсказку. Но на самом деле причина, по которой мне нужен этот объект, заключается в том, чтобы получить аннотации этого метода, и знаете что? Они стираются. Метод перехвата имеет annos. Если я сделаю это не так, как pjp.getTarget().getClass().getMethod(), то annos будет возвращен. Все аннотации помечены как @Retention(RetentionPolicy.RUNTIME). Любой совет? - person Oleg; 07.03.2011
comment
Чтобы уточнить: final MethodSignature methodSignature = (MethodSignature)pjp.getSignature(); окончательный метод method = methodSignature.getMethod(); final Annotation[] annos = method.getAnnotations(); переменная annos будет нулевой, если я получу объект метода таким образом. Я пометил ваш ответ как принятый, так как это было целью, но, к сожалению, такой объект бесполезен, если аннотации удалены. Я также пробовал это: @Around(execution(@foo.bar.annotation.MyAnnotation * *.*(..))) не помогло. - person Oleg; 08.03.2011
comment
@Oleg см. stackoverflow.com/questions/2559255/ - person Bozho; 08.03.2011
comment
@Bozho Спасибо, я видел этот пост, когда искал нулевую проблему. Перепробовал все из того поста. Я все еще получаю нулевое или странное исключение (при привязке аннотации). И мои аннотации помечены как RetentionPolicy.RUNTIME. Это не помогает, к сожалению. - person Oleg; 08.03.2011
comment
@Олег - это странно. Попробуйте очистить окружающую среду. - person Bozho; 08.03.2011
comment
Нет, чистка не помогла. Я сейчас далеко от источников моего проекта, но когда я вернусь к ним, я отпишусь), я думаю, что нашел кое-что, нужно сначала попробовать. - person Oleg; 08.03.2011
comment
@Олег, хорошо. поделитесь результатом, интересно :) - person Bozho; 08.03.2011

Хорошо, теперь моя проблема решена. После более внимательного изучения того, что возвращает методSignature.getMethod(), я заметил, что он возвращает интерфейс вместо реализующего класса, и, конечно же, в интерфейсе не было никаких аннотаций. Это отличается от перехватчиков EJB, где getMethod() возвращает реализующий метод класса.

Итак, окончательное решение таково:

    final String methodName = pjp.getSignature().getName();
    final MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
    Method method = methodSignature.getMethod();
    if (method.getDeclaringClass().isInterface()) {
        method = pjp.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes());    
    }

и, если хотите, вы также можете обрабатывать аннотации интерфейса, если это необходимо.

Также обратите внимание на этот бит: method.getParameterTypes() без этого все равно будет вызывать NoSuchMethodException, поэтому хорошо, что мы можем получить правильную подпись через ((MethodSignature)pjp.getSignature()).getMethod();

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

Кстати, собственный подход Spring без AspectJ:

public Object invoke(final MethodInvocation invocation) throws Throwable {}

возвращает интерфейс, а не реализует класс. Проверил и для познания.

С наилучшими пожеланиями и спасибо за помощь, я очень ценю это. Олег

person Oleg    schedule 09.03.2011