Summary
Proxies are an important piece in the J2EE puzzle. Since it offers a non-intrusive an more transparent way of weaving in advice/interceptors. However, non of the current proxy implementations utilizes all the rich semantics of AOP (as defined by AspectJ), the expressiveness of a real pointcut pattern language and has the speed of statically compiled code.
AW Proxy is here to try to narrow this gap. It makes use of the rich semantics for AOP in AspectWerkz and is very high-performant due to the use of static compilation (utilizing the AspectWerkz weaver). All wrapped up a very simple and intuitive API.
The need for proxies
The two arguments against load-time weaving that we hear the most are:
- that it is complex - which is an argument that I don't understand, since all you need to do is to add one single VM option when starting up the VM
- that the transparency of proxies is hard to beat - which is an argument that I can fully agree on
Adding the possibility of using AspectWerkz together with proxies is something that Alex and I have been talking about for a long time but never implemented due to lack of time. Well, this weekend I took the time and it was actually more simple than I thought.
After a couple of hours coding (taking the cglib route with its "class proxy", e.g creating a new class on-the-fly that extends the target class and which methods and constructors delegates to the base class' methods, but with the difference that they don't wrap the arguments but pass them on as they are, which gives a lot better performance), I had a working solution.
Introducing AW Proxy
The benefit with using the AspectWerkz Proxies compared to the regular load time weaving is that there are no VM options. You simply create your proxy and before it is returned to you it is weaved with the aspects that happens to be available on the classpath at that time.
The API is pretty straight forward. All you need is in the
org.codehaus.aspectwerkz.proxy.Proxyclass which has the following API:
// Returns a new proxy for the class specified public static Object newInstance(Class clazz); // Returns a new proxy for the class specified, instantiates it by invoking the constructor // with the argument list specified public static Object newInstance(Class clazz, Class[] argumentTypes, Object[] argumentValues); // Same as above, but with optional parameters for caching and making the proxy advisable public static Object newInstance(Class clazz, boolean useCache, boolean makeAdvisable); // Same as above, but with optional parameters for caching and making the proxy advisable public static Object newInstance(Class clazz, Class[] argumentTypes, Object[] argumentValues, boolean useCache, boolean makeAdvisable);
- The makeAdvisable argument decides if the proxy instance should be advisable, e.g. be prepared for runtime programmatic deployment. More on this later in this article.
- The useCache argument decides if the Proxy should try to reuse an already compiled proxy class for the specific target class. If you set this flag to true then a cached class it is returned (if found) e.g. no new proxy class is compiled. This has both benefits and drawbacks. The benefits being that if there is a new aspect that has been deployed that advises a new join point in the target class then these new join points will not be advised (but already advised join points will be redefined). However if you set this flag to false then a new proxy class will be compiled for each invocation of the method, which will make the invocation a bit slower but you will get a completely new proxy class that is advised at all matched join points.
How to use the API?
Here is a little example on how to use the Proxy API:
// creates and instantiates a new proxy for the class Target Target target = (Target) Proxy.newInstance(Target.class, false);
That's it!
This target instance have now been advised with all the aspects that have been found on the classpath that matches the members of the Target class.
Utilizing programmatic runtime deployment
As I said before, when you create your proxy it will get automatically weaved with all the matching aspects that are found (on the classpath). This is most of the time what you want and need, but there might be cases when you want to add a specific feature, at runtime, to a specfific instance only. In these cases you need programmatic runtime per instance deployment.
One of the new features in the AspectWerkz 2 architecture is that it comes with a full-blown interception framework that allows per instance programmatic deployment with most of the AOP semantics preserved.
In short you will get:
Per instance programmatic deployment for before, around, after, after finally and after throwing advice types for call, execution, set and get pointcuts as well as the expressiveness of the AspectWerkz' pointcut pattern language all wrapped up in a very simple and intuitive API.
POJO pojo = new POJO(); // adds tracing to all methods in the 'pojo' instance ((Advisable) pojo).aw_addAdvice( "* *.*(..)", new BeforeAdvice() { public Object invoke(JoinPoint jp) { System.out.println("Entering: " + jp.getSignature().toString()); } } );
The advice "interceptors" that are supported are:
- AroundAdvice - works like a regular interceptor and is invoked "around" or "instead-of" the target method invocation or field access/modification.
- BeforeAdvice - is invoked before the actual member invocation
- AfterAdvice - is invoked after the actual member invocation, is invoked both if the method returns normally or with an exception. E.g. can be seen as being invoked in the finally block.
- AfterReturning - is invoked after the actual method has returned normally
- AfterThrowingAdvice - is invoked after the actual method has returned with an exception
Now we can combine use this feature together with the AW Proxies. All proxies that are created implements the Advisable interface which makes it really easy and straightforward to use them together:
Example
In this example we create and instantiate a new proxy for the class Target, we set it to become advisable (e.g. it will implement the Advisable interface transparently), and then we add an after returning advice to all methods that returns an instance of java.lang.String.
Advisable target = (Advisable) Proxy.newInstance(Target.class, true, true); target.aw_addAdvice( "String *.*(..)", new AfterReturningAdvice() { public void invoke(JoinPoint jp, Object returnValue) { // do some stuff } } );
Benefits
Plain proxies with rich AOP semantics
The benefits are, besides a less intrusive and more transparent approach, that you can utilize the rich semantics for AOP (well, not all, see below for limitations) that are supported AspectWerkz. Such as, a rich pointcut expression language, before/after/around advice, deployment modules defined by the META-INF/aop.xml file etc.
Very high-performant
You will also get the full performance of the AspectWerkz 2 architecture.
If you are interested details of the benchmark we made (and/or run it yourself) then you can read this paper.
But in short AW Proxy is roughly:
- 25 times faster than Spring AOP for before and after advice
- 8 times faster than Spring AOP for around advice
- 16 times faster than dynaop for before and after advice
- 5 times faster than dynaop for around advice
- 4 times faster than straight cglib for before and after advice
- 1.25 times faster than straight cglib for around advice
Limitations
Since it is a proxy approach it only supports execution pointcuts and can only advise methods and constructors that is non-private and non-final. E.g not call, set, get or handler pointcuts. Advice bound to these pointcut types will simply not affect the class being proxied.
Resources
This new feature will be available in AspectWerkz 2.0 RC2 that will be available soon. Until then you can check out the sources from the CVS and build it yourself (by invoking ant dist).
There are some samples in the ./src/samples/examples/proxy dir in the distribution. These can be executed by invoking ant samples:proxy from the command line.
Enjoy.