|
|
Aspect
Oriented Programming (AOP) with .NET and J2EE |
|
|
|
Functional, structured, object oriented programming... The last decades brought many developments and mutations in the way we design and program. And to be honest, the last step (to the Object world) will still take some time since the conceptual gap to cross is quite important.
Anyway, enthusiasts and people who constantly watch technological evolutions will ask : “what's next ?” What is still to be invented, what can we improve in the way we design and code ? Well, according to us, the next big mutation will be the one of Aspect Oriented Programming (AOP).
This article is first an introduction to ideas and motivations which lead to aspect oriented programming. We'll give the context and some definitions, and then we'll have a look at available tools; this will go through a selective panorama of both C#/.NET and Java/J2EE environments, according to our tradition on DotNetGuru.
As a conclusion to this file, we'll go down in the conceptual layers and take an interest in primitive tools that allow to generate and to analyse Java or C# code. The aim will be both to give you the ability to take advantage of those tools in your own projects, and who knows, maybe to implement your own “aspect weaver” (more on this later).
Everything comes from a simple statement : whatever the way we develop, we never manage not to write redundant code. You can be a code or a design guru, be it in procedural programming, functional, by constraint, by predicate, ruled-based or even in object oriented programming, there will always be a situation when you'll have to write repetitive things to manage some recurrent needs.
Let's give some examples, all coming from the Object world. How could we handle the following needs without writing two times the same code :
how to use a log framework in many methods
how to handle a notification list to implement the “Observer” Design Pattern (although .NET event offer a partial answer to this need)
how to implement objects lazy loading from a database when we implement our own data access layer, without any code generation tool
how to handle an efficient object cache, in which each object remembers its state, clean or dirty (ie modified an to be synchronized towards the original data source)
how to replicate the same method (prototype and body) on many objects when an inheritance relationship isn't possible (because of single inheritance maybe) or when it doesn't make sense (according to the “is-a” rule and the “Liskov inversion” principle).
Object oriented programming will help us to put in factor what other kinds of programming force us to repeat through the code of our projects.
The following example is very well known in the Java AOP world and illustrates the problem : in the code of Tomcat JSP/Servlet engine, some technical needs are very localized (reading configuration files, XML parsing), some other are spread over a few classes (HTTP requests management, regular expressions), but there are still other features that are located nearly everywhere in the code (for example : calling the log framework to be able to diagnose problems that occur, as well as to keep a trace of the requests the server handles).
|
|
|
Figure A : Distribution of the code that manages some features in the Tomcat Web Server |
If unfortunately (for the Tomcat project) the log framework evolves, many changes would have to be made, spread over nearly all the code. We can imagine two solutions to this crisis situation :
If the log framework modifications are limited to renaming some methods, applying a simple refactoring would be enough (the major Java IDE help us to rename classes, methods and fields across a whole project. VisualStudio.NET 2003 is far behind on this feature, only Whitbey will integrate refactoring).
If the the change is deeper (for example if you need to set an error level depending on the throwing of an exception...) we'd rather develop an aspect once, which would be centralized, and that could apply to any location where it is needed.
No, since the problem we just described isn't new at all. It already existed when we developed in procedural languages. In fact, it was even worse since we couldn't use inheritance !
Hence, it is absolutely legal to think of using AOP in C, Cobol... And some developers already did it in fact : development teams of the FreeBSD Open Source operating system. The OS is written in C of course, and AOP has been introduced to handle in a uniform way the data pre-fetching”. The tool they used is named AspectC, which is in a re-implementation phase itself : FreeBSD teams have been delighted by the opportunities provided by this tool, and have wished to enhance it in order to implement other aspects in their system.
To sum up, aspect oriented programming is a new technique to put in factor some responsibilities whose implementation is a priori spread over a whole system, be it object oriented.
Nota bene : an AOP synonym that you'll necessarily meet while browsing on the Web is AOSD (for Aspect Oriented Software Development). The reference web site in this domain is http://aosd.net. And as soon as you get to the first page, you'll find a definition for AOP : Aspect-oriented software development is a new technology for separation of concerns (SOC) in software development. The techniques of AOSD make it possible to modularize crosscutting aspects of a system.
Two different approaches can implement the nice concept of AOP. The first one will be to separate aspects from the code on which we want to apply them (code that we'll name the “base code”). A third party language enables us to explicit the relationships between the base code and the aspects. Flexible and very powerful, this approach is sometimes judged as “a bit too magic” : the developer of one particular class may be amazed to see it behave differently from what she has really written, and will constantly wonder “which aspect has been applied to my class ?”. Hence, we'll need efficient traceability tools. AspectJ follows this approach, and since this project is well appreciated and marketed, this approach tends to become “the AOP way”.
Another option would be to develop aspects, and then to “mark” the code using an adapted syntax. The advantage is that the relationship between base code and aspects becomes explicit, and then developers never wonder whether they have to implement a feature or if an aspect will take it in charge : they see which aspects they invoke (which doesn't free them from knowing what the aspects semantics are). This second approach is followed by Microsoft with the introduction of standard .NET Attributes, and by some code generation tools like Xdoclet in Java. The question is : is it still AOP... well, this is a real debate that you can read on the web.
The next sections will present both techniques and give examples of implementations in Java/J2EE as well as in C#/.NET.
[In the whole article, we will go on using the Attribute word for the AOP oriented syntax, and “field” for the attributes of our classes]
Whatever the language they have chosen, nearly every .NET developer already has manipulated .NET Attributes : it is about those little markers we put as declaration headers (they can precede methods, classes, fields, namespaces...) and that enhance these declarations. The simplest example is the one of conditional compilation : suffices to prefix a method declaration by the Attribute [Conditional(“COMPILATION_VAR”)] so that the compiler makes every call to this method conditional (depending on the presence of the COMPILATION_VAR). A simple example will refresh our memory :
#define DEBUG
namespace AOP {
using System;
using
System.Diagnostics;
public class
ConditionalTest {
[Conditional("DEBUG")]
public
static void
DisplayTrace(string
msg){
Console.WriteLine(msg);
}
public
static void
Main(){
DisplayTrace("In the Main()
method");
}
}
}
TestConditional.cs
In this example, we defined the DEBUG variable right in the code, but of course in a real project we'd do that in the project properties or in the compiler's command line properties. This way, we could choose a global compilation mode for the whole project, and in RELEASE mode we could execute only the really required code.
We can say that this constitutes our first Aspect : a recurrent and coherent behavior (to execute or not to execute a method's body) is applied to all the methods that are marked by the [Conditional] Attribute.
Remark : for those who read about the [Conditional] Attribute for the first time in this article, please note that the methods you'll mark this way must return void, and they mustn't have any side effect; otherwise their conditional compilation would have an impact on the basic behavior of the program.
To enhance the semantic of .NET syntactic elements is nice. It would be a pity if this possibility would be only accessible to the framework itself ! Hopefully, it isn't the case : we can develop our own Attributes and we can associate them to any kind of elements in the programming language we use (C#, VB.NET...). How ? That's pretty simple : an Attribute is... a class that inherits from System.Attribute.
For example, suppose we want to “mark” some classes in an object model as persistent classes (in a database). We would start by defining the AutomatiquePersistence Attribute :
namespace AOP {
[System.AttributeUsage(
System.AttributeTargets.Class)]
public
class AutomaticPersistenceAttribute :
System.Attribute{
private bool
lazyLoading;
private string
mappingName;
public
AutomaticPersistenceAttribute (string
mappingName){this.mappingName =
mappingName;}
public
string MappingName
{get{return
mappingName;}}
public bool
LazyLoading{get{ return
lazyLoading;}set{ lazyLoading =
value;}}
}
}
AutomaticPersistenceAttribute.cs
The general naming convention is to suffix this kind of class by the “Attribute” word, so that we can easily distinguish Attribute classes from standard classes. .NET compilers as well will expect your Attributes to be named this way.
On the other hand, you'll notice that the AutomaticPersistenceAttribute is marked itself by a standard Attribute : AttributeUsage. This enables the compiler to check that our attribute will only be used on the targeted syntactic elements – in our example, we limited the applicability of the AutomaticPersistenceAttribute to classes. Hence, it would be illegal (and the compiler will insult us) to apply it to a method, a struct or a namespace.
Well. Our Attribute being developed, let's apply it to a business class (a ShoppingCart for example) :
namespace AOP {
[AutomaticPersistence("ShoppingCart", LazyLoading = true )]
public class ShoppingCart{
// ...
}
}
ShoppingCart.cs
The syntax we used between brackets looks like a call to the constructor of our AutomaticPersistenceAttribute. In fact, it's exactly what happens, at least for the first parameter. As you probably have guessed, the second one corresponds to the invocation of the setter of the LazyLoading property.
This raises two questions :
Where are stored the data we pass as parameters to Attributes ?
When is our Attribute constructor invoked ?
Aswering the first question is trivial, we simply have to analyze the assembly that contains our ShoppingCart class using the ILDASM.exe tool [Translation : in French, ShoppingCart is called “CaddieVirtuel”] :
|
|
|
Figure B : Contents of an assembly whose classes are marked by Attributes |
The truncated value on the right side of the Figure B corresponds to the “ShoppingCart” that we provided between quotes in the C# code. The data we provide to Attributes are stored in the assembly that calls those Attributes.
And to understand when the constructor of AutomaticPersistenceAttribute will really be called, we propose to add a simple System.Console.WriteLine("In the constructor of PersistanceAutomatiqueAttribute");.
Then, we'll implement two tests. The first one will be to instanciate the ShoppingCart class :
namespace AOP {
public class PersistenceManager {
public static void Main(string[] args){
ShoppingCart cart = new ShoppingCart();
}
}
}
PersistenceManager.cs
Result ? Nothing appears on the console. The constructor of our AutomaticPersistenceAttribute hasn't been invoked.
Second try : let's use .NET Reflection to read the meta-data of the VirtualCart class :
namespace AOP {
public class GestionnairePersistance{
public static void Main(string[] args) {
CaddieVirtuel cad = new CaddieVirtuel();
foreach (System.Attribute a in cad.GetType().GetCustomAttributes(true)) {
if (a is PersistanceAutomatiqueAttribute){
PersistanceAutomatiqueAttribute paa =
a as PersistanceAutomatiqueAttribute;
System.Console.WriteLine(paa.NomMapping);
System.Console.WriteLine(paa.LazyLoading);
}
}
}
}
}
GestionnairePersistance.cs
This time, the results is more expressive :
|
|
|
Figure C : The results when we use Reflection on a class that is marked by AutomaticPersistenceAttribute |
So the constructor of a custom Attribute is invoked when we read it thanks to .NET reflection.
Conclusion : Attributes can mark specific locations in the .NET code and can associate meta-data to this mark (stored in the Assembly). But the behavior of an Attribute is... to do nothing till anyone uses reflection on it. Hence, this mechanism can be useful (classes using Reflection can get the meta-data) but not sufficient to implement real Aspects : indeed, how could we install some code before and after the invocation of methods on an object ? How to add a method to a class ? It seems impossible... At least, we lack an element to reach this goal.
Maybe you remember the articles Sami Jaber published about the .NET Remoting framework. It was about clients and servers, proxies and skeletons, channels, messages and MessageSink. We'll take advantage of a part of this infrastructure to put an interceptor between our business object ans its client.
Without getting into deep details (that you can read here), suffices to understand that :
when we invoke a method on a “normal” object, the invocation is direct and without intermediary. Hence, it is very efficient, but not adapted to the installing of Aspects.
When we invoke a method on an object that
inherits from System.ContextBoundObject, the invocation is
“objectized”. What we mean is that the invocation is
transformed (or “marshaled”) into an object of type
IMessage. This marshaling is implemented by a peer of objects
from the .NET Framework : the transparent proxy and the real
proxy.
Then, the IMessage goes through one or many
MessageSinks that can decide to trigger code before
transferring the message to the next MessageSink, or after getting
the result message (on the way back). In a Pattern language, we
would say that the MessageSinks constitute a chain of
responsibility. In this chain, every actor can trigger code when it
intercepts messages, before transferring them to the next one in the
chain... till we reach the object itself. But an actor can also
decide to stop the message propagation in order to forbid a method
invocation to some classes, or to any client which wouldn't have
correctly initialized a security context. The following diagram
tries to sum up this sequence of actions in a more graphical way :
|
|
|
Figure D : .NET interception infrastructure |
Hence, it would be enough to install a custom MessageSink before every business object in order to be able to trigger code before and after methods invocations or even attributes access. This technique is very powerful... and we propose to lead you to put it in practice through a simple example : an interceptor that will count the number of access (methods invocations, attributes access) on a target object.
namespace AOP {
using System;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
// Our interceptor
public class DNGAspectCompteur : ImessageSink{
private int nbHits;
private IMessageSink suivant;
public DNGAspectCompteur(IMessageSink suivant){
this.suivant = suivant;
}
public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink msgSink) {
throw new Exception("Pas implémenté");
}
public IMessageSink NextSink {
get{ throw new Exception("Pas implémenté"); }
}
public IMessage SyncProcessMessage(IMessage msg) {
IMessage resultat = null;
nbHits++;
//
We could put some code before the invocation
resultat =
suivant.SyncProcessMessage(msg);
// We could put some code after the invocation
return resultat;
}
~DNGAspectCompteur() {
Console.WriteLine ("Nombre total d'accès à l'objet : {0}", nbHits);
}
}
}
DNGAspectCompteur.cs
As for the target object, there is no complexity except that it must inherit from ContextBoundObject.
namespace AOP {
using System;
[DNGCompteur]
public class DNGObservable : ContextBoundObject{
public int i = 5;
public void Test(){
Console.WriteLine("dans test");
}
public static void Main(){
DNGObservable o = new DNGObservable();
o.i = 4;
o.Test();
o.i = 5;
}
}
}
DNGObservable.cs
As you have probably guessed, we associate our interceptor (DNGAspectCompteur) to the DNGObservable class thanks to the [DNGCompteur] Attribute. And here is the most tricky part : the DNGCompteur Attribute is a bit special, since it has to be triggered automatically (it is out of question to way for some other object to come and read our Attribute using Reflection !). This is only possible if :
The Attribute inherits from ContextAttribute instead of Attribute
The target object must inherit from ContextBoundObject, otherwise our Attribute wouldn't be awakened automatically.
Let's go : here is the code we have to write to associate our Attribute (DNGCompteur) to the interceptor (DNGAspectCompteur):
namespace AOP {
using System;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
// Factory d'intercepteurs
public class DNGAspectCompteurProperty :
IContextProperty, IcontributeObjectSink{
public IMessageSink GetObjectSink (System.MarshalByRefObject o, IMessageSink next) {
return new DNGAspectCompteur(next);
}
public void Freeze(Context ctx){}
public bool IsNewContextOK(Context ctx){return true;}
public string Name{get{return "dngproperty";}}
}
// Installe la factory d'intercepteurs dans l'infrastructure d'interception .NET
[AttributeUsage(AttributeTargets.Class)]
public class DNGCompteurAttribute : ContextAttribute {
public DNGCompteurAttribute() : base("dngcontext"){}
public override void GetPropertiesForNewContext(IConstructionCallMessage ccm) {
ccm.ContextProperties.Add(new DNGAspectCompteurProperty ());
}
}
}
DNGObservable.cs
To better understand the previous code, let's play the role of the CLR, and start at the Main method:
When the business class DNGObservable is instantiated, the CLR sets up a context because the class inherits from ContextBoundObject.
Then it runs across the list of Attributes associated with the DNGObservable class, looking for those inheriting from ContextAttribute
When it finds our [DNGCompteur], it instantiates the associated DNGCompteurAttribute and invokes the GetPropertiesForNewContext method on this object
GetPropertiesForNewContext instantiates a property of type DNGAspectCompteurProperty and associates it with the context of the DNGObservable object. This property becomes an interceptor factory : the .NET infrastructure will interact with it to instantiate MessageSinks.
Once everything is initialized, we get back to the Main method :
The instruction o.i = 4; invites the .NET infrastructure (transparent proxy and real proxy) to invoke GetObjectSink on the property that is associated with o's context. This method in turn creates a new instance of DNGAspectCompteur and put it in the MessageSinks chain of responsibility. The whole plumber is setup.
The instruction o.i = 4; is handled by the following chain : transparent proxy, real proxy, DNGAspectCompteur and then the target object. To be precise, the method invoked in DNGAspectCompteur is SyncProcessMessage, hence it is the right place to increment the number of hits on the object o.
The following instructions ( o.Test(); and o.i = 5; ) also get to the target object, and use the same chain of responsibility. They will also increment the hits number.
Before closing our application, the CLR invokes all the finalizers including ~DNGAspectCompteur, in which we print out the number of hits. Without surprise, the result is 3 in our example.
To simplify our example, we didn't talk about the possibility to invoke the target object's methods asynchronously... but as you have read in the code, it wouldn't be very different from what we have shown.
What we call an Aspect (we'll come back on this terminology in the AspectJ presentation below) corresponds to the fact of applying a set of Advice (code enhancements) to a set of elements in the base code (typically to a set of classes).
Using ContextAttributes to install MessageSink interceptors answers a part of the problem : the assignment of aspects to syntactic elements we want to enhance. But we still have to link MessageSinks to the additional code we want to trigger before and after accessing the target objects (the advice).
On this point, many ideas come to our minds :
Either we develop Advice as MessageSinks, which isn't too difficult since we only have to redefine SyncProcessMessage. But this approach is quite closed : to add new features to advice, we'll have to modify the MessageSink's code, recompile it and re-launch the application so that the new MessageSink can be loaded.
Or the MessageSink is a simple controller that will get in a configuration file the name of the classes it will have to associate to a given Aspect. Then, we'll have to agree on the name of a method to call on those classes, and to force this rule by having the classes implement a shared interface. This approach is more sexy since we could add Advice without modifying the MessageSink (controller), and since it also allows us to add new Advice dynamically, during the runtime of our application.
It is exactly the way CLAW works. Unfortunately, this tools isn't supported any more by its author, John Lam (check his weblog to understand why)
Using Attributes and MessageSinks is pretty simple, it is sexy, and completely integrated to the .NET Framework. Unfortunately, it brings some drawbacks :
Introducing intermediaries to intercept each method invocation can seem a heavy overhead. Indeed, the global performances of the application will decrease. To get an idea, it's grosso-modo the behaviour of the major EJB application servers behave in the Java/J2EE world. Very flexible, open but not necessarily very efficient. Of course, performance isn't relevant for every application.
The second problem is bigger : the objects on which we want to install a MessageSink must inherit from ContextBoundObject. And this class inherits from... MarshalByRefObject. Which means that in a .NET Remoting application, every object we'll put an Aspect on will be passed by reference and not by value. This has a huge impact on these applications architecture and on their global performance (again).
The last criticism we can oppose to this strategy is linked to the previous one : our business objects must inherit from ContextBoundObject. And .NET only supports single class inheritance... Are we doomed ? Not totally : it would be enough that the highest level of business object inherit from ContextBoundObject (instead of Object by default). But conceptually, this is quite disturbing since it introduces a dependency between business objects and the technical infrastructure. Which is a real scandal !
To conclude, this technique is acceptable :
For standalone applications
To associate Aspects to objects that are passed by reference in a .NET Remoting application
Supposing we accept to pollute the business model by the inheritance of ContextBoundObject
And supposing that adding (many) intermediaries isn't too heavy for the target applications
According to us, this is unfortunately not the best technique to implement AOP in .NET. However, we wanted to explain it precisely both to satisfy your curiosity and so that you can understand the grounds of our preference for the “AspectJ” like tools.
No panic : the aim isn't to present this tool, which is well known in the Java world and that generates code and deployment descriptors for EJB, Servlets and JSP. To read such a prose, we forward you to the official XDoclet Web site.
No, we only want to draw your attention to the fact that the XDoclet approach in Java is very close to using Attributes in .NET, at least in a syntactic point of view. On the XDoclet front page, they speak about “Attribute Oriented Programming”...
For those who haven't programmed in Java for too long, remember : it is possible to put some special comments just before declaring packages, classes, methods and fields. Those comments are named “JavaDoc comments”. As we can guess, those comments initially aimed at generating (HTML) documentation automatically. But nothing prevents us to rely on the same mechanism to generate other kinds of files, and why not code !? The only things to do are :
To define new keywords (that will be prefixed by @) which will enhance the JavaDoc syntax
To develop a Java class (a Doclet) to interpret those new keywords and to generate something useful.
The rest, ie going through the Java code to look for JavaDoc comments, is fully automated by the associated tool : javadoc.exe. And thanks to a command line option, we can tell it which Doclet to use to interpret special comments.
Developing Enterprise JavaBeans without a tool is heavy, repetitive and boring. Because we have to produce many files (Java classes, XML configuration files) in which there are many redundancies :
The Bean class itself (the implementation of the business object)
The business remote interface for this Bean (describing the set of methods and accessors that can be invoked via RMI)
The business local interface for this Bean (describing the set of methods and accessors that can be invoked locally inside the application server)
The remote interface of the Bean factory (the Home in EJB terminology)
The local interface of the Bean factory
The standard deployment descriptor (ejb-jar.xml)
And very often, a proprietary deployment descriptor (jboss.xml, weblogic-jar.xml...)
The DTO (Data Transfer Object) classes that will allow to pass information by value between the EJB server and its clients.
Well, the times when we had to produce those files manually is bygone. Today, most of EJB developers use either a code generation tool based on UML (such as Together Control Center for example), or the XDoclet. We can't resist to the pleasure to give you a little example of a Java class, enhanced by the required JavaDoc tags to generate all the related files. This example is taken from a little project that manages courses in a training company... :
package beans;
import java.util.Collection;
import javax.ejb.*;
import javax.naming.InitialContext;
import util.XHome;
/**
* @ejb.bean type="CMP" view-type="local" primkey-field = "id"
* schema="Course" name="Course" local-jndi-name="beans/Course"
* @ejb.finder signature = "java.util.Collection findAll()"
* @ejb.finder signature = "beans.CourseLocal findByCode(java.lang.String code)"
* query = "SELECT OBJECT(c) FROM Course c WHERE c.code = ?1"
* @ejb.pk class = "java.lang.Integer"
*/
public abstract class CourseBean implements EntityBean {/**
* @ejb.pk-field
* @ejb.persistent-field
* @ejb.interface-method */
public abstract Integer getId();
public abstract void setId(Integer id);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract String getCode();
/** @ejb.interface-method */
public abstract void setCode(String code);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract int getDuration();
/** @ejb.interface-method */
public abstract void setDuration(int duration);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract String getDurationUnit();
/** @ejb.interface-method */
public abstract void setDurationUnit(String durationUnit);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract String getLabRatio();
/** @ejb.interface-method */
public abstract void setLabRatio(String labRatio);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract String getLanguage();
/** @ejb.interface-method */
public abstract void setLanguage(String language);
/**
* @ejb.persistent-field
* @ejb.interface-method */
public abstract String getTitle();
/** @ejb.interface-method */
public abstract void setTitle(String title);
/** @ejb.relation name="Course-Objectives"
* role-name="Course-Has-Objectives"
* target-ejb="Objective"
* @ejb.interface-method */
public abstract Collection getObjectives();
/** @ejb.interface-method */
public abstract void setObjectives(Collection objectives);
/** @ejb.create-method */
public Integer ejbCreate() throws CreateException {
setId(XHome.getNewPK("Course"));
return null;
}
public void ejbPostCreate() {}
public void setEntityContext(EntityContext context) {}
public void unsetEntityContext() {}
public void ejbRemove() {}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
CourseBean.cs
You'll say “what's the relationship with Aspect Oriented Programming ?”. Well a priori not too much. But if you get closer, this principle looks like using Attributes and interceptors in .NET :
We mark methods and classes to enhance inside the code
Intercepting invocations and enhancing is implemented by the classes that are generated by XDoclet, which fit in the EJB interception : generated interfaces transform our simple Java class into an EJB, and deployment descriptors centralize all the technical configuration (in our example, the query to search a course by code, as well as the definition of a relationship between a course and its pedagogical objectives, which are represented by another EJB).
This idea was close enough in our opinion to deserve being mentioned in an AOP article.
Let's change approaches. After seeing how to use Attributes or JavaDoc comments to decorate code in order to enhance it using intelligent interceptors, we'll focus on AspectJ, whose philosophy advocates a pure separation of the aspects code from the “base” code.
AspectJ is a pretty ambitious project that aims at defining and applying aspects on Java classes. This project has been linving on its own for a long time, but it is now supported by the eclipse.org community. Hence, you'll find the last versions of the tool as well as a plug-in to use it from Eclipse on this site.
AspectJ isn't the only AOP available in the Java world, as you'll see on this tools review. However, it is probably the most advanced and well-known one. Its definitions are accepted by AOP actors, and are taken as “biblical definitions” by the other tools of the same domain (that we'll see later in the .NET world). Then, we propose to have a look at a short glossary :
|
Keyword |
Definition |
|
Advice |
A piece of code (in Java for AspectJ) that is doomed to be inserted into classes of the “base” code. One advice can be associated to a whole set of locations in the code. |
|
Joinpoint |
Identification of a location in the “base” code wherer we'll be able to insert advice. In fact, it is not really a location, but more of a moment, an special event. For example, the moment when a constructor or a method is called, when a field is accessed... |
|
Pointcut |
A pointcut is a precision on a Java event : it allows to identify the invocation of one particular method, on instances of one particular class... AspectJ supports wildcards (* and ..) to identify a set of interception locations in a very precise way. It is also possible to define custom pointcuts, that's to say to name a group of interception locations, in order to simplify the applying of an advice on many locations of the base code. |
|
Aspect |
An aspect groups :
Every aspect is stored in a file named à la Java (same name as the aspect, plus a .java extension) located in a standard package. It really looks like a class, except that the keyword is aspect (of course). |
|
Weaver |
A compiler that applies (or introduces) aspects to the base code |
AspectJ behavior is very simple : nearly everything happens during project compile time. The compilation order is the following :
The AspectJ weaver searches the set of Aspects definitions across the project, and compiles them. More precisely, it pre-compiles every Aspect into a Java standard class, which is in turn compiled to bytecode.
If all aspects are correct, they are applied to base classes. In practice, this means to identify all interception locations in the source code of the target classes, and to insert the associated aspects there. Note that it is possible to add many advice to the same interception location. Code insertion isn't done in the original code itself of course, but in a temporary copy. Hence, there is no pollution of the code by aspects, or any problem to delete a previously applied aspect.
Once the Java code is modified, it is compiled into bytecode as usual.
So at runtime, we execute a bytecode which has been compiled after the aspect weaving. A priori, noting is specific to AspectJ in the code we execute, hence we don't have to put anything in the CLASSPATH. In practice, when we develop more subtle Advice (that use introspection to know on which syntactic element they are applied), we'll need to add a little library (aspectjrt.jar) of 29 ko.
It is possible to use the AspectJ weaver in a command prompt and to develop aspects using any text editor. This will probably be the preferred way for Vi, (X)Emacs, UltraEdit or Nedit fans.
Another technique : AspectJ comes with a graphical aspect browser. Here is a little snapshot of this tool (don't pay attention to the aspect that is open, we'll come back on it later).
|
|
|
Figure E : AspectJ Browser |
But the most interesting way is probably to use the Aspect Plug-in, specifically developed for Eclipse. It allows to :
Create an AspectJ project
Add a set of aspect to the project
Generate a configuration file (.lst) that references the list of aspects the weaver will have to take into account
And then launch the compilation by a simple right-click.
In practince, we found this plug-in quite stable and very well integrated to the Eclipse IDE habits. For example, an aspect compilation error appears as a red little bullet in the margin and in the tasks list, exactly like a standard Java compilation error.
To illustrate this presentation of AspectJ, we propose to write an aspect that will count the number of access to an instance of a Java class, just like we did in the “.NET Attributes and interceptors” section.
Let's start creating an aspect named DNGCompteur. Then we add a pointcut to tell AspectJ where we want to trigger code.
package org.dng.aop.aspects;
public aspect DNGCompteur {
pointcut invocationMethodes():
call(* org.dng.aop..*.*(..)) && !within(DNGCompteur);
before(): invocationMethodes() {
System.out.println("Avant l'acces a une methode");
}
}
}
DNGCompteur.java
The literal translation of this aspect is something like : before() each method call() on an instance of a class located in the package org.dng.aop or in any sub-package (org.dng.aop..*.*), except in invocations that would occur inside the DNGCompteur itself ( !within(DNGCompteur) ), we want to invoke the Advice System.out.println("Avant l'acces a une methode");.
We put the constraint !within(DNGCompteur) to avoid any recursive method invocation...
The following code shows the base code on which we'll apply the preceding aspect :
package org.dng.aop.base;
public class Observable {
private int valeur;
public void test(){
System.out.println("Dans la methode test de Observable");
}
public int getValeur() {
return valeur;
}
public void setValeur(int valeur) {
this.valeur = valeur;
}
}
/******/
public class Test {
public static void main(String[] args) {
Observable obs = new Observable();
obs.test();
System.out.println( obs.getValeur() );
}
}
Observable.java et Test.java
You have noticed that this code is totally naive : it doesn't expect what's going to happen to it. However, after weaving the DNGCompteur aspect, launching the Test.main method gives the following result :
|
|
|
Figure F.1 : Access counter |
Our Advice has a problem : it isn't capable of saying which method is going to be called. To resolve this, it has to use a special kind of introspection using a “magic” object provided by AspectJ : the JoinPoint representation.
Let's modify our aspect :
package org.dng.aop.aspects;
public aspect DNGCompteur {
pointcut invocationMethodes():
call(* org.dng.aop..*.*(..)) && !within(DNGCompteur);
before(): invocationMethodes() {
System.out.println("Avant l'acces a une methode\n\t" + thisJoinPoint);
}
}
DNGCompteur.java
Which has the following impact :
|
|
|
Figure F.2 : Access counter |
Following the same logic, we could weave an aspect to the fields access. You know the principle :
package org.dng.aop.aspects;
public aspect DNGCompteur {
pointcut sollicitationQuelconque():
(call(* org.dng.aop..*.*(..)) ||
get(* org.dng.aop..*))
&& !within(DNGCompteur);
before(): sollicitationQuelconque() {
System.out.println ("Avant l'acces a : \n\t " + thisJoinPoint);
}
}
}
DNGCompteur.java
No surprise, the result also counts fields access on the Observable object (read access only thanks to get() ) :
|
|
|
Figure F.3 : Access counter |
For the moment, we only react to method invocations and fields access. How to enhance our example to count the number of access on one particular object (here, an Observable) ?
You'll say : “if anyone has to bear this responsibility, it must be the Observable object itself”. I totally agree. But it wasn't designed in this perspective... Anyway, we'll use another feature of AspectJ named “Introduction” that will allow us to add a new field to the Observable class as well as a getter to read and display this field value at the end of our program.
package org.dng.aop.aspects;
public aspect DNGCompteur {
private int org.dng.aop.base.Observable.compteurHits;
public int org.dng.aop.base.Observable.getCompteurHits(){
return compteurHits;
}
pointcut sollicitationQuelconque():
(call(* org.dng.aop..*.*(..)) ||
get(* org.dng.aop..*))
&& !within(DNGCompteur);
before(): sollicitationQuelconque() {
System.out.println ("Avant l'acces a : \n\t " + thisJoinPoint);
}
}
}
DNGCompteur.java
Since we are curious, we wanted to have a look at the modifications the weaving made on the Observable class. To do so, we simply decompiled it using jad. The result is self-explanatory :
// Decompiled by Jad v1.5.7f. Copyright 2000 Pavel Kouznetsov.
// Source File Name: Observable.java
package org.dng.aop.base;
import java.io.PrintStream;
import org.aspectj.runtime.reflect.Factory;
import org.dng.aop.aspects.DNGCompteur;
public class Observable{
public Observable(){
DNGCompteur.ajc$interFieldInit$org_dng_aop_aspects_DNGCompteur$org_dng_aop_base_Observable$compteurHits(this);
}
public void test(){
System.out.println("Dans la methode test de Observable");
}
public int getValeur(){
Observable observable = this;
Object aobj[];
org.aspectj.lang.JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, observable, aobj = new Object[0]);
DNGCompteur.ajc$perSingletonInstance.ajc$before$org_dng_aop_aspects_DNGCompteur$154(joinpoint);
return observable.valeur;
}
public void setValeur(int arg0){
valeur = arg0;
}
public int getCompteurHits(){
}
private int valeur;
public int ajc$interField$org_dng_aop_aspects_DNGCompteur$compteurHits;
public static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0;
static {
Factory factory = new Factory("Observable.java", Class.forName("org.dng.aop.base.Observable"));
ajc$tjp_0 = factory.makeSJP("field-get", factory.makeFieldSig("2-valeur-org.dng.aop.base.Observable-int-"), 11);
}
}
}
Observable.jad, decompiled after the weaving of DNGCompteur
As we can see, DNGCompteur is used to insert Advice, but that the field itself is really located in the Observable class.
What remains to be done ? To increment this new field we introduced in the Observable class in our Advice. To do so, the instance of Observable on which we want to work (compteurHits) must be declared in the pointcut, otherwise it wouldn't be accessible from the associated advice :
package org.dng.aop.aspects;
public aspect DNGCompteur {
private int org.dng.aop.base.Observable.compteurHits;
public int org.dng.aop.base.Observable.getCompteurHits(){
return compteurHits;
}
pointcut sollicitationQuelconque(org.dng.aop.base.Observable o):
(call(* org.dng.aop..*.*(..)) ||
get(* org.dng.aop..*))
&& !within(DNGCompteur)
&& target (o);
before(org.dng.aop.base.Observable o): sollicitationQuelconque(o) {
o.compteurHits++;
System.out.println("Nombre hits : "+o.compteurHits);
}
}
DNGCompteur.java
The result makes no surprise :
|
|
|
Figure F.4 : Access counter |
However, it is interesting to note that in the base code, the compteurHits field and its getter become accessible ! Which means it is legal to invoke obs.getCompteurHits(), even though this method hasn't been declared in the Observable class !
AspectJ is very simple to manipulate and proves to be a very powerful tool. After this little usage example, we can ask to ourselves which kind of need can be answered by this tool :
The most frequent answer is “to invoke a log framework”. Indeed, it is very simple to invoke a log method to trace every method invocation in an application. The statistics on logging usage in Tomcat (cf Figure A) are self explanatory : to invoke the framework “by hand”, ie in the code developers write, is not really maintainable. On the other hand, if we use a specific aspect, it can be done with a few centralized lines of code instead of thousands of lines spread over the whole project.
Another recurrent technique in the AOP world is to implement the Observable/Observer design pattern by an aspect. True : to handle a notification list, to offer registration / unregistration methods... is a frequent need but we cannot put it into factor in a base class, since this class would be a purely technical one. And single inheritance would become an annoying constraint. On the other hand, using an aspect to add methods and fields that are necesary to manage subscription / notification on a whole set of classes is trivial.
To make an object distributable becomes very simple : in Java, the only requirement for an object to be distributable is to implement java.rmi.Remote (and then, we can export it using PortableRemoteObject.expotObject()). Now AspectJ can help us to modify the list of interfaces a class implements... The rest is easy : we could take advantage of aspects to implement technical services such as security and transactions management... We kind of re-invent the concept of application server, but without the heaviness EJB development impose on our projects !
Fans of programming by contract will implement pre- and post-conditions as well as class invariants...
There are many example, and you'll probably find innovative AOP applications on your own projects !
One question comes to everybody's mind : “is there an equivalent of AspectJ in the .NET world ?”
Unfortunately, the answer is no. Some projects have started, such as Weave.NET, AspectC#... but the are still at the early stages of development.
AspectC# seems to be the one that seems ahead of the other is the result of a very interesting student thesis that you can read here, and that has been continuated by Donal Lafferty. AspectC# is downloadable for free. It is a simple command-line weaver that reads an XML configuration file to associate aspects to base code. But this product is quite limited for the moment : it doesn't propose any JoinPoint on field access for example, not anything for delegates, structs, Attributes... It is still in alpha version, and its web page isn't very confident in the pace of the development to come... Well we are far away from the comprehensiveness, the support and the permanence of AspectJ.
Hence, we feel a bit of frustration : we are at the beginning of a new software development era, and we cannot try AOP because no tool is available on the .NET platform.
DotNetGuru would like to follow the AOP trend today and provide you with the benefits of our adhesion to this move. Sami Jaber and me have the wish to develop our own aspect weaver (let's call it AspectDNG) and to put its code into LGPL licence.
But as you know, it wasn't DotNetGuru's first objective (or role) to develop tools on the .NET platform. Hence, we would like you to give us some feedback to determine if :
You are interested by AOP ?
You are ready to invest some time and become at least beta-testers of AspectDNG, maybe contributors ?
You think DotNetGuru should play this role of AOP developer on .NET ? Or else we should leave this part to Microsoft or to other OpenSource communities ?
Well, the question is open... It is up to you to decide. You wish is to answer your needs at best, hence it is your responsibility to tell us if this project would have the same added value as the articles we could write instead of developing AspectDNG.
The decision will be taken in some time, depending on the global trend we'll get in the comments of this article. Let's say that the decision will be taken in a purely democratic way, a way we would like the world to follow...
For anyone who could be interested by...
The construction of a .NET weaver
Or by code generation on a project, supposing .NET generation tools (such as OlyMars or DTM) don't answer your needs
... we would like to take a tour of the different techniques that the .NET platform proposes in this domain.
System.Reflection.Emit is a standard API in the .NET framework that enables to generate MSIL code on the fly. It is a very useful tool if you want to create a compiler (a PHP to MSIL compiler for example), but it prooves to be tool low level in the scope of code generation or, more generally, code management.
System.CodeDom is another interface, a higher level one, whose objective is to stand for an abstract syntactic tree (an AST) in form of .NET objects. As you can imagine, CodeDom is to .NET code what the DOM is for XML documents.
The biggest advantage of CodeDom is to be completely abstract, and completely independent from any programming language. This memory representation will then be transformable into C#, VB.NET or any other .NET language supposing we have the right code generator.
A little example will give us an idea of the abstraction level with CodeDom :
namespace AOP {
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
public class CodeDom {
public static void Main(string[] args){
// Unité de compilation ou de génération
codeCodeCompileUnit ccu = new CodeCompileUnit();
// Nouveau namespaceCodeNamespace
ns = new CodeNamespace("DngNamespace");
ccu.Namespaces.Add(ns);
// Nouvelle classe
CodeTypeDeclaration c = new CodeTypeDeclaration("DngClass");
ns.Types.Add(c);
c.IsClass = true;
// Nouvelle méthode
CodeEntryPointMethod m = new CodeEntryPointMethod();
c.Members.Add(m);
// Première manière d'ajouter une instruction
CodeMethodInvokeExpression expr =
new CodeMethodInvokeExpression ( new CodeTypeReferenceExpression("System.Console"),
"WriteLine", new CodePrimitiveExpression("Hello World!") );
m.Statements.Add(new CodeExpressionStatement(expr));
// Seconde manière, quasiment équivalents
m.Statements.Add(new CodeSnippetStatement("System.Console.WriteLine(\"Hello\");"));
// Génération de codeCodeDomProvider comp;
// C#
comp = new Microsoft.CSharp.CSharpCodeProvider();
comp.CreateGenerator().GenerateCodeFromCompileUnit
(ccu, Console.Out, new CodeGeneratorOptions());
// VB.NET
comp = new Microsoft.VisualBasic.VBCodeProvider();
comp.CreateGenerator().GenerateCodeFromCompileUnit
(ccu, Console.Out, new CodeGeneratorOptions());
}
}
}
DNGCompteur.java
The result seems satisfying :
C#
namespace AO {
namespace DngNamespace {
public class DngClass {
public static void Main() {
System.Console.WriteLine("Hello World!");
System.Console.WriteLine("Hello");
}
}
}
}
VB
Option Strict Off
Option Explicit On
Namespace DngNamespace
Public Class DngClass
Public Shared Sub Main()
System.Console.WriteLine("Hello World!")
System.Console.WriteLine("Hello");
End Sub
End Class
End Namespace
A code generated by System.CodeDom
But if you look closer, using a literal line of code in our code generator proves to be fatal in VB.NET. Indeed, the semi-colon at the end of the instruction doesn't respect the VB.NET syntax. Ok, it seems that if we really want to use CodeDom to generate code in multiple languages, we'll have to use the abstraction classes instead of generating text through a “snippet”.
CodeDom seemed interesting for us to generate code. We thought we had found the right tool to develop our aspect weaver. Hence, we tried to do the contrary : to read code (C# or VB.NET) to generate automatically a CodeDom tree in memory. And yes, there is a CodeDomProvider which has a CreateParser() method. Well, this is a trap : this method, in the standard framework, always returns a null (for C# and for VB.NET). Microsoft doesn't provide any parser implementation in standard. Anyway, someone else has probably implemented this for his own needs on a project... Indeed, you'll fine an OpenSource implementation of a C# that generates CodeDom trees : CS CODEDOM (available on http://ivanz.webpark.cz/csparser.html and on http://sourceforge.net/projects/cscodedomparser/). This implementation relies on MCS, the C# parser that provides the Mono project...
Well, CodeDom seems full of promises, but it lacks a set of parsers for standard .NET languages : C#, VB.NET, MC++, Jscript.NET and J#.
Aspect Oriented Programming is orthogonal to the other programming techniques (object, procedural...) and can be used as complement. Our feeling is that its future can be glorious since it resolves in a very simple and elegant way some problems that are not solvable without it, problems that put the maintainability of our big project at risk.
Putting into factor technical needs and weaving code to a set of classes in a project is a natural approach. It is fully integrated in the Java world, and will probably be in a short future in .NET.
The next step will be to get accustomed to using aspects, the same way we got accustomed to using objects. We are confident in the fact that using aspects the right way will take some time. As for objects, it will be simpler if we create and learn some new Design Patterns...
Author : Thomas GIL
Translator : Thomas GIL
Copyright January 2004
An excellent site on AOP : http://www.aosd.net/