简体   繁体   中英

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

I want to call each getter of Argument object of each method of Controller of Spring framework through Spring AOP. But when I call the getter, there is an exception which message is "object is not an instance of declaring class".

What am I wrong?

Thanks in advance.

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

I think your intention is to print all parameter types (class names) and for each parameter its properties via getter methods, assuming it is a Java Bean and has such getters. There are a few problems with your approach, though:

  • Some classes have methods beginning with get which take parameters. You will get java.lang.IllegalArgumentException: wrong number of arguments when trying to call such methods, eg public void java.lang.String.getBytes(int,int,byte[],int) . Thus, you need to exclude such methods by filtering for those without parameters because property getters have no parameters.

  • More subtly, you only want to filter for public methods because property getters are always public. There might be other, non-public get* methods which you ought to skip. Thus, you should call getMethods() instead of getDeclaredMethods() because the latter also gives you protected and private methods. Besides, you would get exceptions when trying to call non-public methods from other classes via reflection without making them accessible first via method.setAccessible(true) .

  • Some methods matching these criteria might still not return anything you are interested in, such as

    • public byte[] java.lang.String.getBytes() for String objects and
    • 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 .
  • Some classes might not expose all their properties via getters because they are no Java Beans. But okay, let us assume this is not a problem.

  • You could use a StringBuilder instead of a StringBuffer because with you local variable you do not need a thread-safe class.

  • Why not add the method name or its full signature to your string buffer/builder? You named the variable methodFullName , after all, so why skip the method name?

  • Last, but not least, there is a bug in your code which you might not have introduced in the first place if you had named your variables more clearly:

    • You declare Class<?>[] parameters = methodSignature.getParameterTypes(); and then consequently use for (Class<?> parameter: parameters) .
    • You should have named them parameterTypes and then parameterType .
    • In that case you might have noticed that calling method.invoke(parameter) is actually method.invoke(parameterType) , which makes no sense because the first argument to invoke must be the actual object you want to invoke the method upon, not its type .
    • But it is easy to understand that this glitch went unnoticed because you might have thought that parameter is the actual parameter (aka method argument) you want to invoke the setter upon, which is is not. You are iterating over parameter types (classes).
    • The parameter objects themselves can be found via joinPoint.getArgs() . So you need the corresponding object from that Object[] with the exact same index as the current object from methodSignature.getParameterTypes() you are dealing with.
    • This is what causes your java.lang.IllegalArgumentException: object is not an instance of declaring class .

I am not sure why you want to use such a contrived approach for logging extensive details about your method calls. According to your pointcut you want to do that for all your controllers, ie you will use it often. It is kind of slow because it uses reflection, so it is up to you to decide if it is worth doing it in the first place. But assuming you answer that with yes, you could fix your code as follows.

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 in AspectJ you need to exclude via execution(.static * *(..)) , which I am not doing here so as not to change your original pointcut.

I am also adding additional log output (always beginning with "#") so you can see what is going on because you are not logging method names.

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

As you can see, I am filtering out getClass and also get* methods with parameters, but in the following log you will see that String.getBytes() is still being called and its result (a byte array's toString representation) printed for String parameters. So this solution is not perfect and I don't like it anyway, I just wanted to answer your question.

A better alternative would be to make sure that your classes have meaningful toString() methods, then you could just print the arguments from joinPoint.getArgs() instead of calling getter methods on each of them.

#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!

Update: If you prefer a more readable solution involving Java streams with filters, you can also do it like that (additional debug statements removed):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM