Invocation replacement
And now, something completely different:) Proxetta provides proxy mechanism that may replace almost any method invocation, including object creation (i.e. constructors) with custom method call. For example, it is possible to replace invocation of some interface methods with some custom implementation. Or, ahead to more loose-coupled code, it is possible to replace object creation with a factory method that returns wired and populated objects.
Usage
Usage is identical to how Proxetta works with dynamic proxies - we just need to provide different types of aspects: InvokeAspect
. InvokeAspect
defines the pointcut, i.e. the method invocation that should be replaced and the advice, i.e. destination method that should be invoked instead.
Here is an example of proxy creation:
Invoke aspect is set here on all invocations of method named foo()
. Invocations will be replaced with the static method Replacer.bar()
. Now, let's apply the proxy:
The instance of One
is now proxified. If class One
looks like this:
it will be modified: foo
method call will be replaced and generated bytecode will look like this:
Nice!
Another example
Let's replace all calls to System.currentTimeMillis()
with custom method. For the purpose of this example, let's say that we have a class like this:
Ok, here is how to build a proxy class:
What we have here? We created InvokeAspect
that will be applied on all top-level methods of the target class (TimeClass
in this example); and in the pointcut we defined what invocation to replace and the replacement call. Now, the code will call MySystem.currentTimeMillis
instead of System
's' method in all top-level methods of TimeClass
.
Remember:
Methods apply
and builder
define where to apply the proxy (the target), pointcut
defines what invocation to replace in target and the replacement call. {: .attn}
Simple, right :)
InvokeInfo and InvokeReplacer
InvokeInfo
contains a lot of information about the methods being invoked. It is used to determine if some method call should be replaced or not. Using InvokeInfo
we can get class name, method signatures, arguments etc. of the invoked methods; so we can decide if some invocation is a target one, one that should be replaced.
InvokeReplacer
holds information about the replacement method - the one that will be invoked instead of the target method. Replacement method is defined as a class name and a method name.
Replacement methods must be static! {: .attn}
Moreover, using InvokeReplacer
we can instruct to pass some additional arguments to replaced method, depending of the target method's signature.
Dynamic replacements
Because the replacement method is defined as a string, we can build them dynamically, as in the following example:
Here all method invocation are replaced with Replacer
methods which names contains original method name and number of arguments. For example, call to foo("xxx")
is replaced with Replacer.foo1("xxx"
).
Replacements
There are several different types of invocation in Java, so there are as many different replacement points in Proxetta
. Each replacement method must take the same number of arguments and must return the same type of result. When replaced method is an instance method, there will be an additional argument holding the reference to the instance.
Virtual invocation
This is a simple method call on an instance, as in above example. Replacement method receives following arguments:
reference to instance of method owner (in the above example its
two
)all arguments of the replaced method
Static methods
Static methods are replaced without any additional arguments.
Interface methods
Similar to virtual methods, interface method calls are replaced with method call that receives an additional argument, that is interface implementation.
Constructors
Constructors' replacement methods doesn't receive any additional argument and must return the created instance. Important: due to VM bytecode, when replacing constructors, there must be a replacement method for each present constructor!
Replacing constructors creation with method invocation might be a powerful feature. Let's take an example:
If we call method One#example()
it would, obviously, print "null
". Now, let's replace the constructor call with Proxetta:
Now we are replacing all constructors with Replacer#new${ClassName}
methods. For example:
If we run proxified class One
, this time we will have the result hello
. And we didn't touch the source of One
!
Additional arguments
The replacement method receives all arguments as a replaced method, plus the reference to target instance if available. However, we can instruct InvokeReplacer
to provide more arguments in replacement methods. Here are the available additional arguments:
owner name
method name
method signature
reference to this
target class
All additional arguments are placed at the end, after existing method arguments.
Under the hood
How does the Proxetta do all this? It creates an identical subclass as a target, but with replaced method invocations.
Because of the nature of subclass, there are some VM limitations that we have to be aware of.
Don't call super()
Because super.super
is not provided by VM.
Constructors are executed twice
Since subclass copies constructors too, they will be executed as well as the target class constructors. So all initialization will be executed twice, once for proxified class, then for the target class. Therefore, do not put heavy-duty initialization in the constructor.
Last updated