Поскольку пользователь daniu спросил, как использовать args()
, вот MCVE с использованием AspectJ (не Spring AOP, но там будет работать тот же синтаксис pointcut):
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
@SomeAnnotationType
public class Application {
public void doSomething() {}
public void doSomething(List<String> names) {}
public void doSomethingDifferent(List<String> names) {}
public void doSomethingInteresting(String... names) {}
public void doSomethingElse(List<Integer> numbers) {}
public void doSomethingGeneric(List objects) {}
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Albert Einstein");
names.add("Werner Heisenberg");
List<Integer> numbers = new ArrayList<>();
numbers.add(11);
numbers.add(22);
Application application = new Application();
application.doSomething();
application.doSomething(names);
application.doSomethingDifferent(names);
application.doSomethingInteresting("Niels Bohr", "Enrico Fermi");
application.doSomethingElse(numbers);
application.doSomethingGeneric(names);
application.doSomethingGeneric(numbers);
System.out.println();
for (String name : names)
System.out.println(name);
System.out.println();
for (Integer number : numbers)
System.out.println(number);
}
}
Без учета каких-либо аспектов, журнал консоли выглядит так:
Albert Einstein
Werner Heisenberg
11
22
Теперь мы добавляем аспект, аналогичный daniu, просто используя args()
, чтобы связать аргумент List<String>
с параметром аспектной точки:
package de.scrum_master.aspect;
import java.util.List;
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 AddToList {
@Pointcut("@within(de.scrum_master.app.SomeAnnotationType) && execution(* *(..)) && args(names)")
public void methodsYouWantToAdvise(List<String> names) {}
@Around("methodsYouWantToAdvise(names)")
public Object addToList(ProceedingJoinPoint thisJoinPoint, List<String> names) throws Throwable {
System.out.println(thisJoinPoint);
names.add(thisJoinPoint.getSignature().getName());
return thisJoinPoint.proceed();
}
}
Обратите внимание:
Вместо within(@de.scrum_master.app.SomeAnnotationType *)
, как предлагает Даниу, я использую более специализированный @within(de.scrum_master.app.SomeAnnotationType)
.
Я добавляю && execution(* *(..))
, потому что в AspectJ есть больше, чем просто execution()
точек соединения, например call()
и я не хочу сопоставлять pointcut дважды за вызов метода + выполнение. В Spring AOP вы можете опустить && execution(* *(..))
, если хотите.
Указатель точек нарезки args(names)
сопоставляет методы только с одним параметром List
, но не методы с дополнительными параметрами. Если вы хотите использовать все методы сопоставления, в которых первый параметр равен List
, но могут следовать и другие параметры, просто используйте args(names, ..)
.
При компиляции этого аспекта с помощью компилятора AspectJ вы увидите предупреждение: unchecked match of List<String> with List when argument is an instance of List at join point method-execution(void de.scrum_master.app.Application.doSomethingGeneric(List)) [Xlint:uncheckedArgument]
. Что это значит, мы увидим через минуту.
Теперь давайте посмотрим на журнал консоли:
execution(void de.scrum_master.app.Application.doSomething(List))
execution(void de.scrum_master.app.Application.doSomethingDifferent(List))
execution(void de.scrum_master.app.Application.doSomethingGeneric(List))
execution(void de.scrum_master.app.Application.doSomethingGeneric(List))
Albert Einstein
Werner Heisenberg
doSomething
doSomethingDifferent
doSomethingGeneric
11
22
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at de.scrum_master.app.Application.main(Application.java:37)
Как видите, pointcut сопоставляет методы только с одним параметром List<String>
и исключает, например, doSomethingElse(List<Integer>)
, , но также соответствует doSomethingGeneric(List)
, то есть метод с необработанным универсальным типом. Он даже совпадает с ним дважды, как при вызове с параметром List<String>
, так и с параметром List<Integer>
.
Теперь это, в основном, проблема не AspectJ, а ограничение универсального языка Java, называемое стиранием типов. Вы можете гуглить это, если хотите, было бы не по теме, чтобы объяснить это здесь подробно. В любом случае, обычно это означает, что во время выполнения вы можете добавить что-либо в общий список, JVM не знает, что вы можете добавлять строку в список целых чисел, что в точности и делает аспект в этом случае. Таким образом, когда позже в цикле for мы предполагаем, что все элементы списка являются целыми числами, мы получаем исключение, которое вы можете увидеть в журнале консоли выше.
Теперь давайте просто изменим последний цикл for следующим образом:
for (Object number : numbers)
System.out.println(number);
Затем исключение исчезает и выводится цикл for:
11
22
doSomethingGeneric
Теперь, что касается исходного вопроса, у нас нет проблем с дженериками, все гораздо проще. Pointcut будет выглядеть примерно так:
@Pointcut("@within(org.springframework.stereotype.Service) && execution(* *(..)) && args(connection, ..)")
public void methodsYouWantToAdvise(String connection) {}
Это должно соответствовать обоим getArticles(..)
методам в приведенном выше примере, но что тогда? Обратите внимание, что код, который вы хотите выделить, не полностью идентичен. Один раз у вас есть удостоверение личности, а один раз нет. Так что либо вы создаете два pointcuts + соответствующие советы (вы также можете встроить pointcut, вам не нужно указывать их отдельно, если вы не используете их повторно), либо вы делаете некрасивые вещи if-else и снова получаете второй, необязательный параметр через getArgs()
. Я думаю, вам следует использовать два совета, потому что вы также вызываете два разных перегруженных клиентских метода Feign с разными сигнатурами (то есть разными списками параметров и разными типами возвращаемых данных).