-
Notifications
You must be signed in to change notification settings - Fork 0
Reflection
Aide Reflection contains set of classes to simplify your work with reflection.
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 are wrapper functions in which your methods will be wrapped. There are 2 types of invokers exist:
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.
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.
Invokers creation is simple:
- Create interface. Invokers can be created ONLY from interfaces
- Define methods in new interface
- Annotate them with
@Invoker
or@ExactInvoker
- 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
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);
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:
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.
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);
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);
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.
Exact wrapping has no difference to simple wrapping except method naming. Use wrapExact
instead of wrap
.
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
stores information about wrapper and can delegate invocation to ArgumentMatcher
, so you don't need to choose wrapper method, just call MethodHolder.invoke()
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 ArgumentMatcher
s for each method. All of this provides the most generic way to call wrappers.
All process looks like this:
- Create wrapper function
- Create
MethodHolder
with wrapper function inside - Call
MethodHolder.invoke()
-
ArgumentMatcherHolder
will search forArgumentMatcher
suitable for given wrapper -
ArgumentMatcher
will call wrapper function properly and return result back toMethodHolder.invoke()
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])
)
);
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 wrapping is exactly the same as wrap
and wrapExact
except return type: it will be MethodHolder
. See example in wrap
section.