«объект не является экземпляром объявления класса» при вызове метода Method.invoke () с JoinPoint в @Before Spring AOP - PullRequest
0 голосов
/ 30 сентября 2019

Я хочу вызывать каждый получатель объекта Argument каждого метода Controller of Spring framework через Spring AOP. Но когда я вызываю метод получения, возникает исключение, сообщение «объект не является экземпляром объявления класса».

В чем я не прав?

Заранее спасибо.

@Before("execution(* *(..)) && within(@org.springframework.stereotype.Controller *)")
public void beforeController(JoinPoint joinPoint) {
    MethodSignature methodSignature =  (MethodSignature)joinPoint.getSignature();
    StringBuffer methodFullName = new StringBuffer("");
    Class<?>[] parameters = methodSignature.getParameterTypes();
    for (Class<?> parameter : parameters) {
        methodFullName.append("\t" + parameter.getName() + "\n");
        Method[] methods = parameter.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("get")) {
                try {
                    Object object = method.invoke(parameter);
                    methodFullName.append("\t\t" + object.toString() + "\n");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    System.out.println(methodFullName.toString());
}

1 Ответ

0 голосов
/ 01 октября 2019

Я думаю, что вы намереваетесь напечатать все типы параметров (имена классов) и для каждого параметра его свойства с помощью методов получения, предполагая, что это Java Bean и имеет такие методы получения. Однако у вашего подхода есть несколько проблем:

  • В некоторых классах есть методы, начинающиеся с get, которые принимают параметры. Вы получите java.lang.IllegalArgumentException: wrong number of arguments при попытке вызвать такие методы, например public void java.lang.String.getBytes(int,int,byte[],int). Таким образом, вам необходимо исключить такие методы путем фильтрации тех, у которых нет параметров, потому что методы получения свойств не имеют параметров.

  • Более тонко, вы хотите фильтровать только для открытых методов, потому что методы получения свойств всегда являются открытыми,Могут быть и другие, не публичные get* методы, которые вы должны пропустить. Таким образом, вы должны вызывать getMethods() вместо getDeclaredMethods(), поскольку последний также предоставляет вам защищенные и приватные методы. Кроме того, вы можете получить исключения при попытке вызова непубличных методов из других классов с помощью рефлексии, не делая их доступными сначала через method.setAccessible(true).

  • Некоторые методы, соответствующие этим критериям, могут все еще не возвращатьвсе, что вас интересует, например

    • public byte[] java.lang.String.getBytes() для String объектов и
    • public final native java.lang.Class java.lang.Object.getClass() для всех типов объектов, поскольку они наследуют этот метод от своего корневого суперкласса Object.
  • Некоторые классы могут не раскрывать все свои свойства через геттеры, поскольку они не являются Java-бинами. Но хорошо, давайте предположим, что это не проблема.

  • Вы можете использовать StringBuilder вместо StringBuffer, потому что с вашей локальной переменной вам не нужен потокобезопасныйclass.

  • Почему бы не добавить имя метода или его полную подпись в ваш строковый буфер / компоновщик? В конце концов, вы назвали переменную methodFullName, так почему же пропустить имя метода?

  • И последнее, но не менее важное: в вашем коде есть ошибка, которую вы, возможно, не ввели впервое место, если вы назвали свои переменные более четко:

    • Вы объявляете Class<?>[] parameters = methodSignature.getParameterTypes(); и затем, следовательно, используете for (Class<?> parameter : parameters).
    • Вы должны были назвать их parameterTypes, а затемparameterType.
    • В этом случае вы могли заметить, что вызов method.invoke(parameter) на самом деле method.invoke(parameterType), что не имеет смысла, поскольку первый аргумент invoke должен быть реальным объектом, который вы хотите вызватьметод, а не его тип .
    • Но легко понять, что этот сбой остался незамеченным, потому что вы могли подумать, что parameter - это фактический параметр (он же аргумент метода), который вы хотитечтобы вызвать сеттер, который не является. Вы перебираете типы параметров (классы).
    • Сами объекты параметров можно найти с помощью joinPoint.getArgs(). Поэтому вам нужен соответствующий объект из этого Object[] с тем же индексом, что и текущий объект из methodSignature.getParameterTypes(), с которым вы имеете дело.
    • Это то, что вызывает ваше java.lang.IllegalArgumentException: object is not an instance of declaring class.

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

Обратите внимание, что я использую чистый пример Jave + AspectJ, а не Spring AOP, поэтому в моем примере вы также видите вывод дляstatic main метод, который вы бы не использовали в Spring AOP, потому что там вы можете перехватывать только нестатические методы, которые в AspectJ вам необходимо исключить с помощью execution(!static * *(..)), который я здесь не делаю, чтобы не изменять исходный pointcut.

Я также добавляю дополнительный вывод журнала (всегда начинающийся с «#»), чтобы вы могли видеть, что происходит, потому что вы не регистрируете имена методов.

package de.scrum_master.app;

public class MyBaseBean {
  protected int id;

  public MyBaseBean(int id) {
    this.id = id;
  }

  public int getId() {
    return id;
  }
}
package de.scrum_master.app;

public class Person extends MyBaseBean {
  private String name;
  private String address;
  private int zipCode;
  private String city;

  public Person(int id, String name, String address, int zipCode, String city) {
    super(id);
    this.name = name;
    this.address = address;
    this.zipCode = zipCode;
    this.city = city;
  }

  public String getName() {
    return name;
  }

  public String getAddress() {
    return address;
  }

  public int getZipCode() {
    return zipCode;
  }

  public String getCity() {
    return city;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", address=" + address + ", zipCode=" + zipCode + ", city=" + city + "]";
  }
}
package de.scrum_master.app;

import org.springframework.stereotype.Controller;

@Controller
public class Application {
  public static void main(String[] args) {
    new Application().printPerson(new Person(11, "John Doe", "123 Main St", 12345, "Hometown"));
    new Application().doSomething("Hello world!", 3);
  }

  public void printPerson(Person person) {
    System.out.println(person);
  }

  public void doSomething(String message, int repetitions) {
    for (int i = 0; i < repetitions; i++)
      System.out.println(message);
  }
}
package de.scrum_master.aspect;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class MyAspect {
  @Before("execution(* *(..)) && within(@org.springframework.stereotype.Controller *)")
  public void beforeController(JoinPoint joinPoint) {
    System.out.println("#" + joinPoint);
    Object[] args = joinPoint.getArgs();
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    StringBuilder methodFullName = new StringBuilder(methodSignature + "\n");
    Class<?>[] parameterTypes = methodSignature.getParameterTypes();
    int argsIndex = -1;
    for (Class<?> parameterType : parameterTypes) {
      argsIndex++;
      methodFullName.append("\t" + parameterType.getName() + "\n");
      System.out.println("  #" + parameterType);
      Method[] methods = parameterType.getMethods();
      for (Method method : methods) {
        String methodName = method.getName();
        if (methodName.startsWith("get") && !methodName.equals("getClass") && method.getParameterCount() == 0) {
          System.out.println("    #" + method);
          try {
            Object object = method.invoke(args[argsIndex]);
            methodFullName.append("\t\t" + object.toString() + "\n");
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }
    System.out.println(methodFullName.toString());
  }
}

Как видите, я отфильтрую getClass, а также get* методы с параметрами, но в следующем журнале вы увидите, что String.getBytes() все еще вызывается и его результат (байтовый массив toStringпредставление) печатается для String параметров. Так что это решение не идеально, и мне все равно оно не нравится, я просто хотел ответить на ваш вопрос.

Лучшей альтернативой было бы убедиться, что у ваших классов есть значимые toString() методы, тогда вы могли быпросто выведите аргументы из joinPoint.getArgs() вместо вызова методов-получателей для каждого из них.

#execution(void de.scrum_master.app.Application.main(String[]))
  #class [Ljava.lang.String;
void de.scrum_master.app.Application.main(String[])
    [Ljava.lang.String;

#execution(void de.scrum_master.app.Application.printPerson(Person))
  #class de.scrum_master.app.Person
    #public java.lang.String de.scrum_master.app.Person.getName()
    #public java.lang.String de.scrum_master.app.Person.getAddress()
    #public int de.scrum_master.app.Person.getZipCode()
    #public java.lang.String de.scrum_master.app.Person.getCity()
    #public int de.scrum_master.app.MyBaseBean.getId()
void de.scrum_master.app.Application.printPerson(Person)
    de.scrum_master.app.Person
        John Doe
        123 Main St
        12345
        Hometown
        11

Person [id=11, name=John Doe, address=123 Main St, zipCode=12345, city=Hometown]
#execution(void de.scrum_master.app.Application.doSomething(String, int))
  #class java.lang.String
    #public byte[] java.lang.String.getBytes()
  #int
void de.scrum_master.app.Application.doSomething(String, int)
    java.lang.String
        [B@78c03f1f
    int

Hello world!
Hello world!
Hello world!

Обновление: Если вы предпочитаете более удобочитаемое решение, включающее потоки Java с фильтрами, вы также можете сделать это так (дополнительные операторы отладки удалены):

package de.scrum_master.aspect;

import java.lang.reflect.Method;
import java.util.stream.Stream;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class MyAspect {
  @Before("execution(* *(..)) && within(@org.springframework.stereotype.Controller *)")
  public void beforeController(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Class<?>[] parameterTypes = methodSignature.getParameterTypes();
    StringBuilder logMessage = new StringBuilder(methodSignature + "\n");
    int argsIndex = 0;

    for (Class<?> parameterType : parameterTypes) {
      Object parameter = args[argsIndex++];
      logMessage.append("\t" + parameterType.getName() + "\n");
      Stream.of(parameterType.getMethods())
        .filter(method -> method.getParameterCount() == 0)
        .filter(method -> method.getName().startsWith("get"))
        .filter(method -> !method.getName().equals("getClass"))
        .forEach(method -> logMessage.append("\t\t" + invokeMethod(method, parameter) + "\n"));
    }

    System.out.println(logMessage.toString());
  }

  private Object invokeMethod(Method method, Object targetObject) {
    try {
      return method.invoke(targetObject);
    } catch (Exception e) {
      e.printStackTrace();
      return "<ERROR>";
    }
  }
}
...