簡體   English   中英

“對象不是聲明類的實例”在 @Before Spring AOP 中使用 JoinPoint 調用 Method.invoke()

[英]“object is not an instance of declaring class” at calling Method.invoke() with JoinPoint in @Before Spring AOP

我想通過 Spring 框架的 Controller 的每個方法的參數 object 的每個吸氣劑通過 Z38008DD81C2F4D71ZCEOP. 但是當我調用getter時,有一個異常消息是“對象不是聲明類的實例”。

我錯了什么?

提前致謝。

@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());
}

我認為您的意圖是通過 getter 方法打印所有參數類型(類名)和每個參數的屬性,假設它是 Java Bean 並且具有這樣的 getter。 但是,您的方法存在一些問題:

  • 有些類有以get開頭的方法,這些方法接受參數。 嘗試調用此類方法時,您將收到java.lang.IllegalArgumentException: wrong number of arguments ,例如public void java.lang.String.getBytes(int,int,byte[],int) . 因此,您需要通過過濾那些沒有參數的方法來排除這些方法,因為屬性 getter 沒有參數。

  • 更巧妙的是,您只想過濾公共方法,因為屬性 getter 始終是公共的。 您可能應該跳過其他非公開的get*方法。 因此,您應該調用getMethods()而不是getDeclaredMethods() ,因為后者還為您提供了受保護的和私有的方法。 此外,當您嘗試通過反射從其他類調用非公共方法時,您會遇到異常,而無需先通過method.setAccessible(true)訪問它們。

  • 某些符合這些條件的方法可能仍然不會返回您感興趣的任何內容,例如

    • public byte[] java.lang.String.getBytes()用於String對象和
    • public final native java.lang.Class java.lang.Object.getClass() for all object types because they inherit that method from their root super class Object .
  • 某些類可能不會通過 getter 公開其所有屬性,因為它們不是 Java Bean。 但是,好吧,讓我們假設這不是問題。

  • 您可以使用StringBuilder而不是StringBuffer ,因為使用局部變量不需要線程安全的 class。

  • 為什么不將方法名稱或其完整簽名添加到您的字符串緩沖區/構建器? 畢竟,您將變量命名為methodFullName ,那么為什么要跳過方法名稱呢?

  • 最后但並非最不重要的一點是,如果您更清楚地命名變量,您的代碼中可能不會首先引入一個錯誤:

    • 你聲明Class<?>[] parameters = methodSignature.getParameterTypes(); 然后使用for (Class<?> parameter: parameters)
    • 您應該將它們命名為parameterTypes ,然后是parameterType
    • 在這種情況下,您可能已經注意到調用method.invoke(parameter)實際上是method.invoke(parameterType) ,這是沒有意義的,因為invoke的第一個參數必須是您要調用該方法的實際 object,而不是它的類型.
    • 但很容易理解,這個故障沒有引起注意,因為您可能認為parameter是您想要調用 setter 的實際參數(也稱為方法參數),但事實並非如此。 您正在迭代參數類型(類)。
    • 參數對象本身可以通過joinPoint.getArgs()找到。 因此,您需要該Object[]中相應的 object,其索引與您正在處理的methodSignature.getParameterTypes()中的當前 object 完全相同。
    • 這就是導致您的java.lang.IllegalArgumentException: object is not an instance of declaring class

我不確定您為什么要使用這種人為的方法來記錄有關您的方法調用的大量詳細信息。 根據您的切入點,您希望為所有控制器執行此操作,即您將經常使用它。 它有點慢,因為它使用反射,所以首先由您決定是否值得這樣做。 但是假設您回答“是”,您可以按如下方式修復您的代碼。

Please note that I use a pure Jave + AspectJ example, not Spring AOP, which is why in my example you also see output for the static main method, which you would not in Spring AOP because there you can only intercept non-static methods which在 AspectJ 中,您需要通過execution(.static * *(..))排除,我在這里沒有這樣做,以免改變您原來的切入點。

我還添加了額外的日志 output(總是以“#”開頭),所以你可以看到發生了什么,因為你沒有記錄方法名稱。

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());
  }
}

正如你所看到的,我正在過濾掉getClassget*方法,但在下面的日志中你會看到String.getBytes()仍在被調用,並且它的結果(一個字節數組的toString表示)為String參數打印. 所以這個解決方案並不完美,我也不喜歡它,我只是想回答你的問題。

更好的選擇是確保您的類具有有意義的toString()方法,然后您可以從joinPoint.getArgs()打印 arguments 而不是在每個類上調用 getter 方法。

#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>";
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM