Skip to content

Reflection

Danila Rassokhin edited this page Feb 6, 2023 · 7 revisions

Aide Reflection contains set of classes to simplify your work with reflection.

LambdaWrapperHolder

LambdaWrapperHolder allows you to wrap reflected methods into lambda expressions dynamically using LambdaMetaFactory. Such wrapped methods can be called as fast as direct invocation unlike standard reflective call.

LambdaMetafactory is not generic way to call methods, it means you can't just create one wrapper function and use it to call any method. Aide provides you tools to easily create and manage all wrappers and can even choose what wrapper to use.

Invokers

Invokers are wrapper functions in which your methods will be wrapped. There are 2 types of invokers exist:

Invoker

Methods annotated as @Invoker can be used as generic methods. It means they are not tied to specific signature, so one invoker can wrapped many functions. Only return type (void or not) and arguments count matter. Parameter types and return type (if it is not void) should be Object to return any type and accept any types of arguments.

ExactInvoker

Unlike @Invoker functions annotated as @ExactInvoker are strictly binded to their signatures. So they can only wrap methods with same signature. It means everything matter: return type (must be exactly the same, unlike @Invoker), parameter types and their count.

Create invokers

Invokers creation is simple:

  1. Create interface. Invokers can be created ONLY from interfaces
  2. Define methods in new interface
  3. Annotate them with @Invoker or @ExactInvoker
  4. Load methods or full interface to LambdaWrapperHolder

IMPORTANT: If you want to wrap non-static method or constructor of inner class, your invoker must have at least one argument in which caller object will be passed. So if your method takes 1 argument then invoker needs 2 arguments: 1 for caller and 1 for actual parameter

Example

public interface TestInterface {
    // Can wrap only functions which return String and takes only one argument of type int
    @ExactInvoker
    String exact(Object caller, int id);

    // Can wrap any function which return anything and takes no arguments
    @Invoker
    Object call(Object caller)
}

Now load methods or full interface:

// Create holder with default LambdaWrapper initialization
LambdaWrapperHolder lambdaWrapperHolder = LambdaWrapperHolder.DEFAULT;
// Load all annotated methods from TestInterface
lambdaWrapperHolder.add(TestInterface.class);
// Or load methods directly
Method testMethod = ReflectionUtil.getMethod(TestInterface.class, "exact", Object.class, int.class);
lambdaWrapperHolder.add(testMethod);

Wrapping

Aide has built-in LambdaWrapper class with a lot of prepared wrappers which might be enough for most of methods. But you can create and use your own wrapper interfaces. There are 3 types of wrapping:

wrap

Simple wrapping creates wrapper for your method and returns interface instance, that can be used to call wrapped method. This way requires you to know exact wrapper method you need to call, so it is not good solution for runtime method calls.

Internal wrapper

Create some class to wrap method from:

  public static class TestClass {

    public Long get() {
      System.out.println("Getter invoked");
      return 1L;
    }
}

Wrap method with default LambdaWrapper:

// Find getter
Method getMethod = ReflectionUtil.getMethod(TestClass.class, "get");
// Wrap getter
// LambdaWrapper is used as interface by default
WrapperHolder<LambdaWrapper> getterHolder = lambdaWrapperHolder.wrap(getMethod);
LambdaWrapper getter = getterHolder.getWrapper();
// Invoke getter
// Here you need to know method you should call if you 
// have more than 1 wrapper methods in your interface
Long id = getter.get(caller);
System.out.println("Getter result: " + id);

Wrap method with your own interface:

// Create interface
public interface TestInterface {
    @Invoker
    Object get(Object caller);
}
// Load method into LambdaWrapperHolder
Method wrapper = Reflection.getMethod(TestInterface.class, "get", Object.class);
lambdaWrapperHolder.add(wrapper);
// Wrap method
WrapperHolder<TestInterface> getterHolder = lambdaWrapperHolder.wrap(functionMethod, TestInterface.class);
TestInterface getter = getterHolder.getWrapper();
// Call method
Long result = getter.get(caller);

External wrapper

You can use wrapper without saving it into LambdaWrapperHolder. Assume we have TestInterface from example above:

// Get wrapper method
Method getMethod = ReflectionUtil.getMethod(TestInterface.class, "get", Object.class);
// Create metadata for wrapper lambda
LambdaMetadata getMetadata = LambdaMetadata.from(testMethod);
// Wrap method
WrapperHolder<TestInterface> getterHolder = lambdaWrapperHolder.wrap(functionMethod, getMetadata);
TestInterface getter = getterHolder.getWrapper();
// Call method
Long result = getter.get(caller);

wrapExact

Exact wrapping is very specific wrapping type, cause it requires you to know method signature in addition to wrapper method. You should use exact wrapping only if you know what exactly method you want to call.

Example

Exact wrapping has no difference to simple wrapping except method naming. Use wrapExact instead of wrap.

wrapSafe

Safe wrapping is the most comfort and generic way to wrap methods. It provides some compile-time type safety and can automatically choose right wrapper to call.

MethodHolder

MethodHolder stores information about wrapper and can delegate invocation to ArgumentMatcher, so you don't need to choose wrapper method, just call MethodHolder.invoke()

ArgumentMatcher

ArgumentMatcher provides matching between passed arguments and wrapper arguments. It defines how arguments provided to MethodHolder.invoke() will be passed to wrapper method. There many predefined matchers for all LambdaWrapper wrappers, but if you use your own interface you will need to create your own ArgumentMatchers for each method. All of this provides the most generic way to call wrappers. All process looks like this:

  1. Create wrapper function
  2. Create MethodHolder with wrapper function inside
  3. Call MethodHolder.invoke()
  4. ArgumentMatcherHolder will search for ArgumentMatcher suitable for given wrapper
  5. ArgumentMatcher will call wrapper function properly and return result back to MethodHolder.invoke()
Create ArgumentMatcher

All matchers are stored in ArgumentMatcherHolder. To add new matcher you need to create MatcherSignature and ArgumentMatcher itself. MatcherSignature stores interface declaring wrapping function and MethodSignature for wrapping function.

// Find real method and wrapper method
Method realMethod = ReflectionUtil.getMethod(TestClass.class, "callAction");
Method wrapperMethod = ReflectionUtil.getMethod(TestWrapper.class, "action", Object.class);

// Create signature for wrapper
// It is important to use exactly MethodSignature.fromWrapper
MethodSignature methodSignature = MethodSignature.fromWrapper(wrapperMethod);
// Create signature for matcher
MatcherSignature<TestWrapper> matcherSignature = new MatcherSignature<>(TestWrapper.class, methodSignature);

ArgumentMatcherHolder.INSTANCE.addMatcher(matcherSignature, 
   // Create matcher
   // You will get your wrapper interface instance, 
   // original Executable (method or constructor) and arguments array
   // holder type will be equal to type in MatcherSignature generic param
   // So you don't need to cast it
  (holder, original, args) -> 
    // Matcher should return some value
    // If your method returns void,
    // then you can return null or use ArgumentMatcher.voidable(Action)
    ArgumentMatcher.voidable(
    // Call right wrapper function from your interface and place arguments in right order
      () -> holder.getWrapper().action(args[0])
    )
);

Internal wrapper

Assume we have some class:

 public static class TestClass {

    public void set(String arg) {
      System.out.println("Setter invoked: " + arg);
    }

}

Wrap method using default LambdaWrapper:

// Find setter
Method setMethod = ReflectionUtil.getMethod(TestClass.class, "set", String.class);
// Wrap setter with LambdaWrapper
// MethodHolder for you wrapper
// LambdaWrapper - wrapper interface type
// TestClass - caller type (from which class method will be called)
// Void - original method return type
MethodHolder<LambdaWrapper, TestClass, Void> setHolder = lambdaWrapperHolder.wrapSafe(setMethod);
// Invoke setter
setHolder.invoke(caller, "hello");

External wrapper

External wrapping is exactly the same as wrap and wrapExact except return type: it will be MethodHolder. See example in wrap section.