Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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 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!
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
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.
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"
).
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.
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 are replaced without any additional arguments.
Similar to virtual methods, interface method calls are replaced with method call that receives an additional argument, that is interface implementation.
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
!
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.
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.
Because super.super
is not provided by VM.
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.
Read method parameter names
Java compiler doesn't store method parameter names in the class file and we can't retrieve them using reflection. Actually, the names are stored in the LocalVariableTable
attribute of the method, if a class is compiled with debug symbols on (i.e., javac -g). The LocalVariableTable
attribute is not accessible using reflection, but it can be read using the bytecode parsing library like ASM.
Paramo is a little tool that extracts method or constructor parameter names from bytecode debug information in runtime.
Paramo works only if debug information is available in class files (compile with: javac -g
).
Using Paramo is very simple: just pass Method
or Constructor
to the static method Paramo#resolveParameters
. It returns MethodParameter[]
array with method parameter information, or an empty array if the method/constructor does not have any parameter.
MethodParameter
is a simple POJO that holds two information about the method parameter:
parameter name
bytecode signature, including the generic information!
MethodresolveParameters
does not cache anything; every time invoked it will examine the bytecode again. For efficient usage, wrap it and cache results. {: .attn}
In contrast to the rest of Jodd, Paramo is written to be 'raw', without many helper methods. This is done by purpose, since getting parameter names is usually part of some higher-level logic, that should wrap it in the way that serves the best to that logic.
Let's say we have the following class:
We will assume that we got references to Method
s and Constructor
using reflection (and with tool such ReflectUtil#findMethod
). Now, let's read parameter names:
Proxetta creates dynamic proxies in run-time, providing efficient support for aspects; using developer-friendly syntax.
A proxy class is a class that wraps or extends the target class specified at runtime. Proxy Aspect contains advice and pointcut rules for applying advice. Proxy Advice is the code portion of an aspect, i.e. the logic that replaces crosscutting concern. A pointcut is a set of points in the application where advice should be applied, i.e. which methods will be wrapped by proxy.
Proxetta offers 3 different proxy types, that we call: Proxy
,
Wrapper
and InvokeReplacer
.
ProxyProxetta
is Proxetta that extends the target class (light red).
Pointcut methods (dark red) are overridden in proxy subclass (light blue)
and called when required from the advice (dark blue).
Therefore, instead of working with the target instance, we have proxified instance. It will be of the same type as the target (since it’s a subclass).
WrapperProxetta
is Proxetta that creates a separate, wrapper class
(light blue) from the target class (light red). The wrapper class holds the
reference of the target class instance. Pointcut methods (dark red)
are called from the wrapper advice methods (dark blue). But wrapper
also delegates call to other target methods.
Proxetta offers furthermore several ways how the wrapper behave, and if it implements some interface.
Finally, InvokeProxetta
is a bit different and unique type of proxy. It
creates a clone class and replaces invocation of target methods in the
method code with advised method invocation. In other words, it simply
replaces method calls in the target class.
First, Proxetta is developer-friendly - the syntax is clean and fluent, which makes everyday development easy.
However, what is unique is the way how pointcuts and advices are defined.
Proxetta pointcuts are defined in pure Java, no custom proprietary notation is used. Therefore, any matching mechanism may be used: regular expression, wildcards; configuration might be stored in external (XML) files and so on.
Proxetta's advices uses a completely different concept than any other AOP library. Advices are written in the same way as users would write real subclass. Instead of using a custom class for accessing proxified method and its arguments, they will be replaced directly on the usage place.
This unique approach makes Proxetta fast. Very fast.
The Invocation of replacements proxies is another unique feature of Proxetta.
Because of its unique approach for advices definition, Proxetta is very fast. Compared to one of the most used code generation libraries, CGLIB, the invocation of proxified method is significantly faster.
Methref is a tool that gives you strong-type references to method names.
Sometimes you need to refer to a method in our code using their names. Usually, in this cases, method names are stored as strings and they are not immune to method name changes and typing errors.
Methref is a tiny and cool utility, build on Proxetta, that provides strongly-typed references to method names. Here is how it works.
First, we create Methref
object and call method name
on method reference. Could it be simpler?
If a method has arguments, just pass whatever: usually null
or zeros:
Two important things to remember:
Although there is a target method invocation, method code is NOT
executed! Proxy just returns null
or a method name if methods return type is a String
.
Create new Methref
where needed. However, they can be stored and reused.
With Methref you can actually do a lot of magic :) Previous, simple usage can be split into several steps:
This approach is made for cases when method invocation happens in different place of your code, where you don't want to expose Methref
.
Imagine that you are creating several Methref
in one place of code. Then, a user of your code will call a method on one of those proxies, but you don't have control over which. There is a way to detect the Methref
from the proxy by calling method isMyProxy
. Furthermore, there is a static
method lastName(instance)
that works like the Methref#lastName()
.
As I said, it can be used for magic :) Believe it or not, with Methref you can create, for example, strongly typed SQL-alike syntax directly with your code.
Enjoy!
Proxetta pointcuts are defined in pure Java. Pointcut is defined by simple functional interface: ProxyPointcut#apply()
that should return true
if target method is a pointcut and if proxy should be applied on it. Since pointcut applying is done in pure Java, user may define pointcuts in various proprietary ways.
The following example shows the definition of a pointcut on all methods that are marked with custom annotation: @Log
.
You can combine ProxyPointcut
definitions using and()
and or()
, for example:
MethodInfo
argument provides various information about the target method: class name and signature, method name, number of arguments, access flag, return type... It also returns ClassInfo
and AnnotationInfo
: additional class and annotation information. All this information may be use to define on which methods to apply a proxy. And since all this information is stored as simple types and Strings, pointcut definition becomes easy, but powerful - all in plan Java.
When scanning classes for pointcuts, Proxetta examines all methods of target class and of all its superclasses, up to the Object
. Final methods are ignored, since they can't be overridden. Moreover, only public methods of superclasses are available for proxyfication if not overridden. {: .attn}
All class and method information is available as strings (due to bytecode manipulation).
Proxetta advices are unique: they are written in the same way as one would write overridden method in a subclass. Accessing target method or info is done with special macro markers.
Advices implement the ProxyAdvice
interface that has just one method: execute()
. Now, the question is how Proxetta references the proxy target, i.e. the pointcut method that is proxified. Instead of having some custom (handler) object filled with data about the target method (pointcut), Proxetta introduces special 'macro' class ProxyTarget
for this purpose. ProxyTarget
is just a dummy class and all its methods are empty! ProxyTarget
methods serves just as macros that will be replaced by appropriate bytecode that does what macro method is specified for! After replacing ProxyTarget
macro methods, all dependencies on ProxyTarget
are gone. Macro methods are replaced with the bytecode that mimic the code that developer would write by himself, if he would like to subclass target class and override target method (pointcut).
Here is the definition of the advice that should log some data (on all log pointcuts):
When Proxetta finds some pointcut, i.e. proxy target method on which to apply this advice, it will replace all ProxyTarget
method invocations (i.e. macros) with appropriate bytecode. For example, ProxyTarget.argumentsCount()
will be replaced by appropriate number of target method arguments. The macro method call is replaced by a number.
To continue the example, if the pointcut is the method in the following class:
then the proxy bytecode generated using above advice will look like:
Replacements occurs in the run-time, during proxy creation, using bytecode manipulation. But don't forget that changes are done in the class, meaning, all the replaced info is on class-level scope, as you would write them before compilation!
All macro methods replacement is done on class-level. Think of it as constant data you put there before compilation! {: .attn}
And one more thing, for the sake of good health:
It is advisable to use ProxyTarget
macro-methods just in simple assignment expressions. {: .attn}
During creation of proxy class and methods, Proxetta also copies advice's constructors, static initialization blocks, fields, etc. Advices should be written very carefully, always having in mind that advice's code will be added to the target proxy class. Common mistake is accessing package scoped class from advice: while it is valid for advice, it will be not valid for target class, since it is in different package. Another mistake is usage of static attributes declared in advice's class - since they are copied to every class, each one will have its own static attribute, instead of having one field for all classes. This can be solved by using some external class that will hold this static attribute.
Using inner classes in advices is not supported. {: .attn}
We just didn't want to complicate your world :)
WrapperProxetta
works differently than ProxyProxetta
: instead of extending the target class, it creates new class that delegates call to target instance.
WrapperProxetta
generates new class that delegates calls to target class instance. It is not enough to create a wrapper class and the instance - you must inject the target instead into it, too.
There are 3 ways how to build a wrapper.
Resulting object is custom type that implements all interfaces of the target. All target methods are wrapped.
Similar as above, except the resulting type implements one interface (provided by user). Still, all target methods are wrapped, even those that does not belong to the interface. They can be invoked using e.g. reflection.
Resulting object has one interface, and only interface methods are wrapped.
Here is an example of how to create wrapper class and inject target instance into it.
Similar to Methref
, the Pathref
returns the bean path of the property. The usage is very similar:
Bean path can be used later in reading the property using e.g. BeanUtil
.
Once when pointcuts and advices are defined, it is easy to define a proxy aspect itself:
ProxyAspect
is nothing but a simple holder of the advice and pointcuts, used for creating Proxetta
implementations.
This page describes ProxyProxetta
type of proxies, but many things here are common for other Proxetta types as well. {: .attn}
For each type of Proxetta, there is a static factory method (yes, you can use new
instead if that is your thing):
Each method returns specific Proxetta builder. Just continue using it by following the fluent interface. Yes, it's that simple.
Let's create a new ProxyProxetta
:
Now it's time to configure created ProxyProxetta - or you can just skip the next step and go with the defaults.
Each Proxetta
implementation share the same set of properties.
Specifies 'forced' mode. If true
, the new proxy class will be created even if there are no matching pointcuts. Otherwise, new proxy class will be created only if there is at least one matching pointcut.
Specifies classloaders for loading created classes.
Defines variable proxy class name, so every time when new proxy class is created its name will be different. Therefore, one classloader may load it without a problem. If this flag is not set, proxy class name will be constant; Such class can be loaded only once by a classloader.
Defines class name suffix of generated classes.
When debug folder is set, Proxetta will save all classes upon their creation into that folder. Very useful for debugging purposes!
Finally, once when Proxetta
is created and configured, we can re-use it multiple times to create a factory for proxies. Proxetta proxy factories are responsible for creating proxy classes using bytecode manipulation.
The proxy factory then can be applied to the target class:
The following factory methods are available for creating a proxy class:
create()
- generates class and returns its byte[]
content.
define()
- loads created class bytes and returns as a Class
.
newInstance()
- instantiates default constructor for defined class.
So the previous line becomes:
or, directly an instance:
Note: generated classes do not contain any debug information to avoid ClassFormatError
. Some 3rd party tools (like Emma) may loose some local variable information. {: .attn}
ProxyProxetta
extends a target class. Pointcut method are overridden in proxy. During the execution of adviced code in generated method, target method is called using super
reference. Proxy methods has the same annotations as the target methods.
Proxy class has the same constructors as the target class. It is also of the same type as the target class, so it can easily can be used instead.
Obviously, you can't proxy interfaces or abstract methods.
By default, proxy name is created from target class name, by appending default suffix. Suffix name can be changed. Moreover, if variable names feature is turned on, added suffix is changed each time by appending auto-incrementing number. In all cases, proxy class is in the same package as target class.
Sometimes it is needed to have more control over proxy class name and package. This is especially important when proxyfing JDK classes, since default classloader doesn't allow to instantiate anything from java.*
package.
Proxetta allows to completely control proxy names. Every method for proxy definition and proxy creation accept second argument that defines proxy name in the following ways:
.Foo
(class name starts with a dot) - proxy package name is equal to
target package, just proxy simple class name is set.
foo.
(class name ends with a dot) - proxy package is set, proxy
simple name is create from target simple class name (suffix is
appended).
foo.Foo
- full proxy class name is specified (suffix is appended).
Adding suffix can be also completely disabled, but in that case different package for proxy class must be provided, since it is not possible to define two classes with the same package and simple names.
Proxy name is set on builder using setTargetProxyClassName()
.
It is easy to apply aspects on beans registered in the Petite container transparently. Petite has single point method for beans registration. User may override this method and create proxy for each bean type before it is actually registered in the container.
There is already a class that does that: ProxettaAwarePetiteContainer
.