Получить параметры различных аннотаций в одном Pointcut - PullRequest
0 голосов
/ 20 декабря 2018

Мне нужно регистрироваться всякий раз, когда вызывается RESTendpoint.Я пытаюсь сделать это с помощью весеннего AOP.

Помимо прочего, мне нужно узнать, как называется конечная точка.Т.е. мне нужно прочитать значение аннотации Mapping.

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

Итак, то, что я делал сейчас, в основном то, что было предложено в этом ответе: https://stackoverflow.com/a/26945251/2995907

@Pointcut("@annotation(getMapping)")
    public void getMappingAnnotations(GetMapping getMapping){ }

Затем я передаю getMapping моему совету и получаю его значение.

Чтобы иметь возможность выбрать любое сопоставление, с которым я сталкиваюсь, я следовал принятому ответу на этот вопрос: Spring Aspectj @Before allМетод rest

@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) " +
    "|| @annotation(org.springframework.web.bind.annotation.GetMapping)" +
    "|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
    "|| @annotation(org.springframework.web.bind.annotation.PathVariable)" +
    "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
    "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)"
)
public void mappingAnnotations() {}

Я хотел бы просто написать что-то вроде

public void mappingAnnotations(RequestMapping requestMapping) {}

и затем извлечь из него значение, поскольку все сопоставления являются псевдонимами для RequestMapping.К сожалению, это не сработало.До сих пор похоже, что мне нужно сделать отдельный pointcut для каждого вида отображения, а затем иметь метод для каждого из них (который был бы очень похож - не очень СУХОЙ) или довольно уродливый блок if-else (возможно, я мог бысделайте это переключателем с некоторой чепухой).

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

Ответы [ 2 ]

0 голосов
/ 22 декабря 2018

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

Что касается @RequestMapping, все остальные аннотации @*Mapping являются синтаксическим сахароми задокументировано, что составные аннотации действуют как ярлыки, см., например, @GetMapping:

В частности, @GetMapping является составной аннотацией, которая действует как ярлык для @RequestMapping(method = RequestMethod.GET).

Т.е. сам тип GetMapping аннотируется @RequestMapping(method = RequestMethod.GET).То же самое относится и к другим составным аннотациям (синтаксический сахар).Мы можем использовать это обстоятельство для нашего аспекта.

AspectJ имеет синтаксис для поиска аннотированной аннотации (также вложенной), см., Например, мой ответ здесь .В этом случае мы можем использовать этот синтаксис для общего соответствия всех аннотаций, аннотированных @RequestMapping.

. Это все еще оставляет нам два случая, то есть прямую аннотацию и синтаксическую сахарную аннотацию, но это немного упрощает кодтем не мение.Я придумал этот пример приложения на чистом Java + AspectJ, импортировал только JAR spring-web , чтобы иметь доступ к аннотациям.В противном случае я не использую Spring, но pointcut и рекомендации будут выглядеть одинаково в Spring AOP, вы даже можете исключить часть && execution(* *(..)) из первого pointcut, потому что Spring AOP в любом случае не знает ничего, кроме pointcut выполнения (но AspectJ делает и будеттакже соответствует call(), например).

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

package de.scrum_master.app;

import org.springframework.web.bind.annotation.*;
import static org.springframework.web.bind.annotation.RequestMethod.*;

public class Application {
  @GetMapping public void get() {}
  @PostMapping public void post() {}
  @RequestMapping(method = HEAD) public void head() {}
  @RequestMapping(method = OPTIONS) public void options() {}
  @PutMapping public void put() {}
  @PatchMapping public void patch() {}
  @DeleteMapping @Deprecated public void delete() {}
  @RequestMapping(method = TRACE) public void trace() {}
  @RequestMapping(method = { GET, POST, HEAD}) public void mixed() {}

  public static void main(String[] args) {
    Application application = new Application();
    application.get();
    application.post();
    application.head();
    application.options();
    application.put();
    application.patch();
    application.delete();
    application.trace();
    application.mixed();
  }
}

Обратите внимание, как я смешал разные типы аннотаций и как я добавил еще одну аннотацию @Deprecated к одному методу, чтобы иметь отрицательный контрольный пример для аннотации, которая нас не интересует.

Аспект:

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Aspect
public class RequestMappingAspect {

  @Before("@annotation(requestMapping) && execution(* *(..))")
  public void genericMapping(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
    System.out.println(thisJoinPoint);
    for (RequestMethod method : requestMapping.method())
      System.out.println("  " + method);
  }

  @Before("execution(@(@org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
  public void metaMapping(JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint);
    for (Annotation annotation : ((MethodSignature) thisJoinPoint.getSignature()).getMethod().getAnnotations()) {
      RequestMapping requestMapping = annotation.annotationType().getAnnotation(RequestMapping.class);
      if (requestMapping == null)
        continue;
      for (RequestMethod method : requestMapping.method())
        System.out.println("  " + method);
    }
  }

}

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

execution(void de.scrum_master.app.Application.get())
  GET
execution(void de.scrum_master.app.Application.post())
  POST
execution(void de.scrum_master.app.Application.head())
  HEAD
execution(void de.scrum_master.app.Application.options())
  OPTIONS
execution(void de.scrum_master.app.Application.put())
  PUT
execution(void de.scrum_master.app.Application.patch())
  PATCH
execution(void de.scrum_master.app.Application.delete())
  DELETE
execution(void de.scrum_master.app.Application.trace())
  TRACE
execution(void de.scrum_master.app.Application.mixed())
  GET
  POST
  HEAD

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

Что вы думаете?


Обновление:

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

package de.scrum_master.app;

import org.springframework.web.bind.annotation.*;
import static org.springframework.web.bind.annotation.RequestMethod.*;

public class Application {
  @GetMapping public void get() {}
  @PostMapping(value = "foo") public void post() {}
  @RequestMapping(value = {"foo", "bar"}, method = HEAD) public void head() {}
  @RequestMapping(value = "foo", method = OPTIONS) public void options() {}
  @PutMapping(value = "foo") public void put() {}
  @PatchMapping(value = "foo") public void patch() {}
  @DeleteMapping(value = {"foo", "bar"}) @Deprecated public void delete() {}
  @RequestMapping(value = "foo", method = TRACE) public void trace() {}
  @RequestMapping(value = "foo", method = { GET, POST, HEAD}) public void mixed() {}

  public static void main(String[] args) {
    Application application = new Application();
    application.get();
    application.post();
    application.head();
    application.options();
    application.put();
    application.patch();
    application.delete();
    application.trace();
    application.mixed();
  }
}
package de.scrum_master.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Aspect
public class RequestMappingAspect {

  @Before("@annotation(requestMapping) && execution(* *(..))")
  public void genericMapping(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
    System.out.println(thisJoinPoint);
    for (String value : requestMapping.value())
      System.out.println("  value = " + value);
    for (RequestMethod method : requestMapping.method())
      System.out.println("  method = " + method);
  }

  @Before("execution(@(@org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
  public void metaMapping(JoinPoint thisJoinPoint) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
    System.out.println(thisJoinPoint);
    for (Annotation annotation : ((MethodSignature) thisJoinPoint.getSignature()).getMethod().getAnnotations()) {
      RequestMapping requestMapping = annotation.annotationType().getAnnotation(RequestMapping.class);
      if (requestMapping == null)
        continue;
      for (String value : (String[]) annotation.annotationType().getDeclaredMethod("value").invoke(annotation))
        System.out.println("  value = " + value);
      for (RequestMethod method : requestMapping.method())
        System.out.println("  method = " + method);
    }
  }

}

Журнал консоли будет выглядеть следующим образом:

execution(void de.scrum_master.app.Application.get())
  method = GET
execution(void de.scrum_master.app.Application.post())
  value = foo
  method = POST
execution(void de.scrum_master.app.Application.head())
  value = foo
  value = bar
  method = HEAD
execution(void de.scrum_master.app.Application.options())
  value = foo
  method = OPTIONS
execution(void de.scrum_master.app.Application.put())
  value = foo
  method = PUT
execution(void de.scrum_master.app.Application.patch())
  value = foo
  method = PATCH
execution(void de.scrum_master.app.Application.delete())
  value = foo
  value = bar
  method = DELETE
execution(void de.scrum_master.app.Application.trace())
  value = foo
  method = TRACE
execution(void de.scrum_master.app.Application.mixed())
  value = foo
  method = GET
  method = POST
  method = HEAD

Обновление 2:

Если вы хотите скрыть отражающие элементы, используя AnnotatedElementUtils и AnnotationAttributes Spring, как первоначально предложено @ M.Прохоров , вы можете использовать тот факт, что с getMergedAnnotationAttributes вы действительно можете делать покупки за один раз как для оригинальной аннотации RequestMapping, так и для синтаксиса, такой как GetMapping, получая информацию как о методе, так и о значении в одном,объединенный атрибут объекта.Это даже позволяет вам исключить два разных случая получения информации и, таким образом, объединить два совета в один, например, так:

package de.scrum_master.aspect;

import static org.springframework.core.annotation.AnnotatedElementUtils.getMergedAnnotationAttributes;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * See https://stackoverflow.com/a/53892842/1082681
 */
@Aspect
public class RequestMappingAspect {
  @Before(
    "execution(@org.springframework.web.bind.annotation.RequestMapping * *(..)) ||" +
    "execution(@(@org.springframework.web.bind.annotation.RequestMapping *) * *(..))"
  )
  public void metaMapping(JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint);
      AnnotationAttributes annotationAttributes = getMergedAnnotationAttributes(
        ((MethodSignature) thisJoinPoint.getSignature()).getMethod(),
        RequestMapping.class
      );
      for (String value : (String[]) annotationAttributes.get("value"))
        System.out.println("  value = " + value);
      for (RequestMethod method : (RequestMethod[]) annotationAttributes.get("method"))
        System.out.println("  method = " + method);
  }
}

Вот, что у вас есть: СУХОЙ, как вы изначально хотели, довольно читабельный и обслуживаемый.код аспекта и простой доступ ко всей (мета) информации аннотации.

0 голосов
/ 21 декабря 2018

Вы можете принять JoinPoint в любом аспекте Spring, и из этого вы можете извлечь вызов Signature (который, в вашем случае, всегда должен быть MethodSignature).Затем вы можете работать с этой подписью, чтобы получить метод, который был вызван.

Получив метод, вы можете использовать API мета-аннотации Spring для получения всех соответствующих атрибутов, которые вы хотите получить из аннотации сопоставления.

Пример кода:

@PutMapping(path = "/example", consumes = "application/json")
void exampleWebMethod(JsonObject json) {
  /* implementation */
}

/**
 * Your aspect. I used simplified pointcut definition, but yours should work too.
 */
@Before("@annotation(org.springframework.web.bind.annotation.PutMapping)")
public void beforeRestMethods(JoinPoint jp) {
    MethodSignature sgn = (MethodSignature) jp.getSignature();
    Method method = sgn.getMethod();

    AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(
            method,
            RequestMapping.class
    );

    // and a simple test that this works.
    assertEquals(new String[] {"/example"}, attributes.getStringArray("path"));
    assertEquals(new String[] {"application/json"}, attributes.getStringArray("consumes"));

    // notice that this also works, because PutMapping is itself annotated with
    // @RequestMethod(method = PUT), and Spring's programming model lets you discover that

    assertEquals(new RequestMethod[] {RequestMethod.PUT}, (Object[]) attributes.get("method"));
}

Если вы действительно хотите, вы можете также сделать Spring sythnesize для вас аннотацией, например так:

RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(
        method,
        RequestMapping.class
);

Затем Spring создаст проксион реализует интерфейс аннотаций, который позволяет вызывать методы, как если бы это была реальная аннотация, полученная из метода, но с поддержкой метааннотаций Spring.

...