Wednesday, July 23, 2008

Dynamic AOP Tutorial - Part 2

In this second part of the dynamic AOP tutorial, we will show how you can perform dynamic AOP through our API. This is currently the only available way of using dynamic AOP in standalone applications.

The JBoss AOP API


Aspects, Interceptors and Advices

All aspects, advices, and interceptors are represented by factories. Notice that, internally, JBoss AOP does not differentiate advices from interceptors. They are both represented as interceptor instances, which results in uniform, transparent handling of interceptors and advices. This way, an interceptor chain is used for intercepting a joinpoint every time it is executed, and may contain even typed advices.
For that, JBoss AOP provides a factory capable of transforming advices into interceptors, the AdviceFactory.
Take a look at the main classes used to represent those elements, all located in the org.jboss.aop.advice package:

  • AspectFactory: responsible for creating an instance of an aspect. While you can provide your own implementation of this interface, the most commonly used implementation is the GenericAspectFactory, that creates aspect instances given the class of the aspect to be created.
  • AspectDefinition: this is how aspect factories are managed internally. Whenever you declare an aspect, JBoss AOP creates an AspectDefinition. This definition contains the aspect factory, the aspect scope, and the aspect name.
  • InterceptorFactory: responsible for creating instances of interceptors. To represent a simple interceptor, the GenericInterceptorFactory is used; the AdviceFactory represents advices.

The next example illustrates how you use those classes to represent an advice:
// aspect class
public class MyAspect
{
  public void helloWorld()
  {
    System.out.println(“Hello world!”);
  }

  public Object helloWorld2(Invocation invocation) throws Throwable
  {
    System.out.println(“Hello world!”);
    return invocation.invokeNext();
  }
}
-----
  public static void main(String [] args)
  {
    AspectFactory aspectFactory = new GenericAspectFactory(MyAspect.class, null);
    AspectDefinition aspectDefinition = new AspectDefinition(“myAspect”, Scope.PER_INSTANCE, aspectFactory);
    AdviceFactory adviceFactory = new AdviceFactory(aspectDefinition, “helloWorld”, AdviceType.BEFORE);
    ...
  }

In the first part of the example, we declare the MyAspect class, with two advices: helloWorld and helloWorld2. In the second part, we represent the aspect MyAspect and the advice helloWorld using the classes we have just learned. First, an aspect factory is created. The constructor of GenericAspectFactory must receive the class of the aspect, and an xml element. This second parameter allows to pass extra configuration to the aspect class. Since we are not loading anything from an xml file, the second parameter must be null.
Next, we create an aspect definition with the factory, defining also the name of our aspect (could be any unique name you wanted), and the scope of the aspect. Finally, we create an advice factory. This factory's constructor receives the aspect definition, the name of the advice, and the type of the advice. A second constructor is also provided. If you are declaring around advices, you can omit the advice type, as follows:
// declare the helloWorld2 advice as being of the default advice type, which is around
new AdviceFactory(aspectDefinition, “helloWorld2”);

Naturally, creating those elements is not enough, you must deploy them through our API. We will see how to do this in the next topics.

Binding Aspects to Pointcuts

Advices and interceptors are useless unless they are bound to a pointcut expression, indicating when they should be called. This takes us to the most important class of the dynamic AOP API, the org.jboss.aop.advice.AdviceBinding. This class contains a pointcut expression and a series of interceptor factories. Optionally, it can also contain a cflow expression.
It provides one constructor for dynamic AOP operations:
AdviceBinding(String pointcutExpression, String cflow)

Designed specially for dynamic AOP operations, this constructor creates a binding, defining the pointcut expression and the cflow expression you intend to use. If there is no cflow expression, the second parameter should be null. While there is a second constructor provided by AdviceBinding. It is for internal use only, so always use the one above.

The following methods add advices and interceptors to a binding:
  • addInterceptor(Class clazz)
    Adds an interceptor to the binding. Always use this method when you are binding interceptors.
  • addInterceptorFactory(InterceptorFactory interceptorFactory)
    Adds an interceptor factory to the binding. You must use it to add advices, in the form of AdviceFactory instances (as we have seen above, this class is an implementation of InterceptorFactory, and creates interceptor adapters to call your advice).

AspectManager

Most of the functionalities provided by JBoss AOP have an common entry point: the org.jboss.aop.AspectManager class. This class is our facade and provides all sort of AOP functionalities. Among them, you can find the dynamic AOP methods:
  • addAspectDefinition(AspectDefinition definition): deploys an aspect
  • addAdviceBinding(AdviceBinding binding): deploys a binding
  • removeAdviceBinding(String bindingName): removes a binding
  • removeAdviceBindings(Collection bindings): removes a collection of bindings.

Advisor and InstanceAdvisor

Every weaved class implements the org.jboss.aop.Advised interface. Through this interface, you can access the class Advisor, a sort of AspectManager with scope limited to that single class, and the InstanceAdvisor, with an even more specific scope, a single instance.
Take a look at the methods you can call on the Advised interface:
  • Advisor _getAdvisor()
  • InstanceAdvisor _getInstanceAdvisor()
The InstanceAdvisor class provides dynamic AOP operations that affect only the instance it advises. Its main methods are:
  • insertInterceptor(Interceptor)
    Inserts an interceptor in the beginning of all interceptor chains associated with the advised instance.
  • appendInterceptor(Interceptor)
    Adds the interceptor to the end of the instance's chains
  • removeInterceptor(String)
    Removes an interceptor that has been inserted or appended. The parameter specifies the name of the interceptor to be removed.

Putting it all together

Take a look at the following example:


AdviceBinding binding = new AdviceBinding(“execution(public !static void Pojo->*(int, long))”, null);
binding.addInterceptor(MyInterceptor.class);
AspectManager.getInstance().addAdviceBinding(binding);

This example adds a binding dynamically. It is the API equivalent of the xml binding below:

<aop>
  <binding pointcut="execution(public !static void Pojo->*(int, long))">
    <interceptor class="MyInterceptor.class/"/>
  </binding>
</aop>
Now, an example with advices:

// declares the aspect class
AspectFactory aspectFactory = new GenericAspectFactory(MyAspect.class, null);
AspectDefinition aspectDefinition = new AspectDefinition(“myAspect”, Scope.PER_INSTANCE, aspectFactory);
AspectManager manager = AspectManager.instance();
// deploys the aspect
manager.addAspectDefinition(aspectDefinition);
// creates the advice factory
AdviceFactory adviceFactory = new AdviceFactory(aspectDefinition, “helloWorld”, AdviceType.BEFORE);
// creates the binding
AdviceBinding binding = new AdviceBinding(“execution(public !static void Pojo->*(int, long))”, null);
binding.addInterceptorFactory(adviceFactory);
// adds the binding
manager.addAdviceBinding(binding);

The example above declares and deploys an aspect, then creates an advice factory to represent an advice of this aspect. This advice factory is added to a new advice binding, that is registered at AspectManager by a call to AspectManager.addAdviceBinding method. It is the API counterpart of the xml below:
<aop>
  <aspect name="”myAspect”" class="”MyAspect”/">
  <binding pointcut="”execution(public !static void Pojo->*(int, long))”>
    <before aspect="”myAspect”" name="”helloWorld”/">
  </binding>
</aop>

To remove the advice binding, you can call the removeAdviceBinding method:
manager.removeAdviceBinding(binding.getName());

Notice that the AdviceBinding.addInterceptor and AdviceBinding.addInterceptorFactory methods can be called only before you deploy your binding. Doing so after that will cause inconsistencies.

Our last example adds an interceptor to a single instance of the class Pojo:
// create Pojo
Pojo pojo = new Pojo();
// retrieve its instance advisor
InstanceAdvisor instanceAdvisor = ((Advised) pojo)._getInstanceAdvisor();
// append an interceptor to the chains of this instance only
instanceAdvisor.append(new MyInterceptor());
// executing someMethod will trigger MyInterceptor
pojo.someMethod();


If you create another instance of Pojo, this instance will not be intercepted by MyInterceptor, as the interceptor has been added to the InstanceAdvisor of pojo:
Pojo otherPojo = new Pojo();
pojo.someMethod(); // will not be intercepted by MyInterceptor


Wrapping up

This second part of the tutorial showed the main classes involved in the dynamic AOP API. To add an aspect at runtime you have to
  1. declare the aspect, by adding an AspectDefinition to AspectManager
  2. create a binding, with the pointcut expression of your choice, and add interceptor factories to it
  3. add the binding to AspectManager (it is at this point that the binding will be deployed to your system)
You have two options to add interceptors:
  • create an AdviceBinding containing interceptors and add it to AspectManager as above
  • use the InstanceAdvisor API

Next Steps

-> Take a look at our dynamic AOP example. It is available as part of our releases. Run the example and play with it:
  • add advices to the advice binding, instead of interceptors;
  • create an advice binding that contains interceptors and advices mixed.
-> Check the JBoss AOP Javadoc to learn more about all methods provided by the InstanceAdvisor class. Edit the dynamic AOP example, this time using the InstanceAdvisor methods you learned. Try removing an interceptor at runtime.
-> Get engaged! Help us design a state of the art dynamic AOP API, which will be part of a future release:
http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4166298#4166298

No comments: